// S.Gupta
// Mar 01,00
// Time: 13:10
// Digital Implementation of conventional PID algorithm
// PIC16C773 microcontroller with 8 MHz crystal frequency
#include <16C773.H>
#BYTE OPTION = 0x81
#bit TOIF = 0x0B.2
#use Delay(Clock=8000000)
#use RS232(Baud=9600, Xmit=PIN_C6, Rcv=PIN_C7, brghlok)
#define BRAKE PIN_B1 // Output to apply brake to motor
#define DIRECTION PIN_B2 // Output to control motor direction
#define OVERHEATED PIN_B0 // Input H-bridge temperature flag
#define ALARM PIN_B4 // Output to buzzer/alarm
// *****************************************************************
// Global Variables
// *****************************************************************
// All the control coefficients are entered as multiples of
// desired values. This provides a hidden decimal point
// to improve calculations
long int KP; // Entered as Kp * 8
long int KI; // KI is entered as Ki * TS
// KI = (Ki * 0.001024) * 256
long int KD; // Entered as (Kd/TS) * 256
// KD = (Kd / 0.001024) * 256
long int TS; // Cycle time
long int V0; // Current output
long int V1; // Last output
long int ER0; // Current error
long int ER1; // Previous error
long int ER2; // [Second] Previous error
int SP_VEL; // Desired velocity
int MV_VEL; // Measured velocity
byte duty;
// *****************************************************************
void initialize(void)
{
// Initialize all variables
byte tmp;
// Arbitrary assignment of controller coefficients
KP = 2; // Proportional Coefficient
KI = 1; // Integral Coefficient
KD = 1; // Derivative Coefficient
V0 = 0; // Current Controller output
V1 = 0; // Previous Controller output
ER0 = 0; // Current error
ER1 = 1; // Last error
ER2 = 0; // Second last error
SP_VEL = 0; // Current desired velocity
MV_VEL = 0; // Current motor velocity
TS = 977; // Sample time 977 ms
duty = 0; // Duty cycle
// Set up peripheral functions
// Set motor direction, and no brake
output_bit(DIRECTION, 1); // Set direction as forward
output_bit(BRAKE, 0); // No brakes
output_bit(ALARM, 0); // Reset alarm
// A buzzer/light can be connected to
// this output
set_pwm1_duty(duty); // Set up 0 duty cycle at the start
setup_ccp1(CCP_PWM); // Configure CCP1 as a PWM
setup_timer_2(T2_DIV_BY_4, 255, 1);
set_pwm1_duty(duty); // This sets duty cycle of 0
// Set up A/D
// First two channels are used to read SP & MV
setup_port_a(ALL_ANALOG);
setup_adc(ADC_CLOCK_INTERNAL);
// Set up timer0 for Sample Clock
// Timer0 overflow every 1 ms Xtal = 8 MHz
tmp = OPTION; // Read option register
tmp = tmp & 0xC0; // Clear PSA bit. Assign Prescaler to Timer0
tmp = tmp | 0x02; // Assign Prescaler (001) to div by 8
// Timer0 will overflow every msec.
#asm
clrwdt // Start watch dog timer
#endasm
OPTION = tmp; // Set option register
}
long int input_num(void)
{
// There is no built in routine to read an integer number through
// serial port.
// Read an integer through serial port
// Routine is terminated
// on receiving a non decimal char
// on receiving 4 chars
// Routine returns a long integer (16 bit value)
short exit_flag; // Local flag to indicate a non decimal char
byte b;
byte d; // Number of digits.
long int n;
exit_flag = 0; // Reset the flag
d = 0;
b = 0;
n = 0;
do
{
b = getch(); // Read a char
if ((b >= '0') && (b <= '9'))
{
b = b - '0';
n = n * 10;
n = n + b;
d = d + 1; // Increment digit counter
if (d > 3) // Exit if four digits received
exit_flag = 1;
}
else // Non decimal char received
exit_flag = 1;
} while (!exit_flag); // repeat if flag is not set
return n;
}
void read_coefficients(void)
{
// Accept the control coefficient through serial port
// and set the global variables
printf("\nEnter KP: ");
KP = input_num();
printf("\nEnter KI: ");
KI = input_num();
printf("\nEnter KD: ");
KD = input_num();
// Display the coefficients to user
printf("\n KP: lu", KP);
printf("\n KI: lu", KI);
printf("\n KD: lu", KD);
}
void main(void)
{
byte tick;
long int ad_result;
long int tmp_x, tmp_y;
long int K1; // First Coefficient
long int K2; // Second Coefficient
long int K3; // Third Coefficient
initialize(); // Initialize variables & setup peripherals
read_coefficients(); // Read the control coefficients
// Coefficients are only read once. Reset
// uC in order to enter new values
// Code can be modified to enter the values
// on the fly and store these in EEPROM
while (1) // Begining of infinite loop
{
while (!TOIF); // Wait till TOIF flag is zero
// It will be set to 1 on timer0 overflow
TOIF = 0; // Reset the TOIF flag
restart_wdt(); // Reset watch dog timer
set_adc_channel(0); // Read SP_Vel
ad_result = Read_ADC();
SP_VEL = adc_result;
set_adc_channel(1); // Read MV_Vel
ad_result = Read_ADC();
MV_VEL = ad_result;
ER0 = SP_VEL - MV_VEL; // Current error
// Calculate output
// Velocity algorithm used
// V0 = V1 + KP*(ER0-ER1) + KI*ER0*TS + KD*(ER0-2*ER1+ER2)/TS;
// V0 = V1 + K1*(ER0-ER1)/8 + K2*ER0/256 + K3*(ER0-2*ER1+ER2)/256;
tmp_x = (ER0 - ER1); // Proportional
tmp_x = KP * tmp_x;
tmp_x = tmp_x >> 3; // Divide by 8
V0 = V1 + tmp_x;
tmp_x = K2 * ER0; // Integral
tmp_x = tmp_x >> 8; // Divide by 256
V0 = V0 + tmp_x;
tmp_x = ER0 + ER2; // Derivative
tmp_y = 2 * ER1;
tmp_x = tmp_x - tmp_y;
tmp_y = tmp_x * K3;
tmp_y = tmp_y >> 8; // Divide by 256
V0 = V0 + tmp_y;
// check the output for bounds
if (V0 < 0)
V0 = 0;
if (V0 > 255)
V0 = 255;
duty = V0;
// Shift Variables
V1 = V0;
ER2 = ER1;
ER1 = ER0;
set_pwm1_duty(duty); // Set output duty cycle
// Housekeeping
// Check temperature flag and if overheated sound a buzzer
// Reset buzzer if temperature flag resets
if (input(OVERHEATED))
output_bit(ALARM, 1);
else
output_bit(ALARM, 0);
// Additional tasks
// Enter Tasks here
// ...
}
}