/** TREAD877.C **/ /************************************************************************/ /** **/ /** PICF877 program to run the treads on my RS "Bedlam" based robot. **/ /** PWM (pulse-width-modulation) is used for motor speed control. **/ /** Two down-pointing InfraRed Proximity Sensors are at the front. **/ /** **/ /** As used by my entry in the "Journey Robot Contest" at the **/ /** RI/SME Student Robotic Engineering Challenge. **/ //* April 8 2001 at Robert Morris College (near Pittsburgh). **/ /** **/ /** Copyright 2001 Brian Reed. All Rights Reserved. **/ /** Feel free to ask for permission to use. www.reedonline.com **/ /** **/ /************************************************************************/ #include "stdio.h" // HTML for this page makes me use the double quotes #include "pic.h" // instead of the <> signs. Replace them. /** Miscellaneous defines **/ #define DIR_FORWARD 1 #define DIR_REVERSE 0 #define IR_THRESHOLD 250 /************************************************************************/ /** Make our own references to some 'F877 registers for readability. **/ /** RED_DIREC is really just RB1, BLU_DIREC is RB2. **/ /** RED_SPEED is CCPR1L, BLU_SPEED is CCPR2L. **/ /************************************************************************/ static volatile bit RED_DIREC @ (unsigned)&PORTB*8+1; static volatile bit BLU_DIREC @ (unsigned)&PORTB*8+2; static volatile unsigned char RED_SPEED @ 0x15; static volatile unsigned char BLU_SPEED @ 0x1B; /************************************************************************/ /** Delay support definitions - All compiler optimizations must be ON! **/ /** The CLK_FREQ definition must match the target device. **/ /************************************************************************/ #ifndef CLK_FREQ #define CLK_FREQ 4MHZ /* Crystal frequency in MHz */ #endif #define MHZ *1000L /* Number of kHz in a MHz */ #define KHZ *1 /* Number of kHz in a kHz */ #if CLK_FREQ >= 12MHZ #define DelayUs(x) { \ unsigned char _uctmp; \ _uctmp = (x)*((CLK_FREQ)/(12MHZ)); \ while (--_uctmp != 0) \ continue; } #else #define DelayUs(x) { \ unsigned char _uctmp; \ _uctmp = (x)/((12MHZ)/(CLK_FREQ))|1; \ while (--_uctmp != 0) \ continue; } #endif /************************************************************************/ /** Initialize the PWM sections of the PIC16F877. **/ /************************************************************************/ void PWM_init(void) { // > Q: Does a TMR2 prescale of 1:4 mean I multiply by 0.25 or 4.0? // > Answer: "multiply by .25 or divide by 4" // The 4 in the equation is because Timer2 runs at Tosc/4 // [[ PWM period= (PR2+1) * 4 * Tosc * (TMR2 prescale) ]] // 1:4 duty cycle for both PWMs CCP1CON = 0x0C; CCP2CON = 0x0C; // Set period register PR2 = 0xFF; // Pre-scale setting & Timer2=ON T2CON = 0x06; // Set two tri-state pin's as output for the dir bits on each tread TRISB1 = 0; // DIR1 bit for RED tread TRISB2 = 0; // DIR2 bit for BLUE tread //Good init code but not needed when doing TRISC=0 in main() //TRISC2 = 0; // PWM1 output pin (pin 17 on 40 pin) //TRISC1 = 0; // PWM2 output pin (pin 16 on 40 pin) // set track speeds to 0 and dir bits to FORWARD RED_DIREC = DIR_FORWARD; BLU_DIREC = DIR_FORWARD; RED_SPEED = 0; BLU_SPEED = 0; } /************************************************************************/ /** Routines to init the A/D sections & get A/D input values. **/ /************************************************************************/ void AD_init(void) { OPTION = 0x87; // Set TMR0 prescaler, and 1:256 ADCON1 = 0x02; // Left justify result, 3 analog channels } unsigned char AD_RA0(void) { ADCON0 = 0x41; // Fosc/8, A/D enabled DelayUs(20); // Allow time for A/D capacitor to charge ADGO = 1; // Set the bit to start the conversion while (ADGO) // Wait for the conversion to complete, continue; // then continue ADCON0 = 0; // Disable A/D return ADRESH; // Return the AD value (just the top 8 bits) } unsigned char AD_RA1(void) { ADCON0 = 0x49; // Fosc/8, A/D enabled DelayUs(20); // Allow time for A/D capacitor to charge ADGO = 1; // Set the bit to start the conversion while (ADGO) // Wait for the conversion to complete, continue; // then continue ADCON0 = 0; // Disable A/D return ADRESH; // Return the AD value (just the top 8 bits) } unsigned char AD_RA2(void) { ADCON0 = 0x51; // Fosc/8, A/D enabled DelayUs(20); // Allow time for A/D capacitor to charge ADGO = 1; // Set the bit to start the conversion while (ADGO) // Wait for the conversion to complete, continue; // then continue ADCON0 = 0; // Disable A/D return ADRESH; // Return the AD value (just the top 8 bits) } /************************************************************************/ /** DelayMs() function **/ /************************************************************************/ void DelayMs(unsigned char ms) { #if CLK_FREQ <= 2MHZ do DelayUs(996); while (--ms); #endif #if CLK_FREQ > 2MHZ unsigned char uc; do { uc = 4; do { DelayUs(250); } while (--uc); } while (--ms); #endif } /************************************************************************/ /** Application main() routine. ***TOP LEVEL OF THE APP*** **/ /************************************************************************/ void main(void) { unsigned char uctmp; // for any quick use of an unsigned char value // Main inits TRISC = 0; // Set PORTC Tri-State bits to outputs (LEDs and PWMs) PORTC = 0; // Clear all bits in PORTC to 0 PWM_init(); // Init Pulse-Width-Modulation sub-system AD_init(); // Init Analog-to-Digital converter sub-system // Display RA0 pot value while waiting until RB0 is pressed. // This allows the user to set fwd/rev speed before running the course. while (RB0 != 0) { // Loop until an RB0 button press uctmp = AD_RA0(); // Get the pot value PORTC = uctmp; // Display the pot value on the LEDs } // Main loop: // Go forward until an IRPD sensor detects the black line, // then backup a bit and pivot left or right and resume forward. while (1) { // Just for fun, flash the two outside LEDs. for (uctmp=0; uctmp<7; uctmp++) { PORTC = 0x01; DelayMs(30); PORTC = 0x80; DelayMs(30); } PORTC = 0; // Clear the LEDs on port C // BOTH TREADS FORWARD RED_DIREC = DIR_FORWARD; BLU_DIREC = DIR_FORWARD; // Get the speed from the potentiometer on RA0 uctmp = AD_RA0(); RED_SPEED = uctmp; BLU_SPEED = uctmp; // This internal loop is always checking the IRPD sensors while (1) { if (AD_RA1() > IR_THRESHOLD) { PORTC = 0x80; // LED output indication // Stop now, then backup&stop, pivot&stop, & return to outside loop RED_SPEED = 0; BLU_SPEED = 0; DelayMs(250); DelayMs(250); // Backup & stop //// GOOD TESTING: BETWEEN 600 AND 1000 MS OF BACKUP RED_DIREC = DIR_REVERSE; BLU_DIREC = DIR_REVERSE; uctmp = AD_RA0(); // Get the speed from the potentiometer on RA0 RED_SPEED = uctmp; BLU_SPEED = uctmp; DelayMs(250); DelayMs(250); DelayMs(250); DelayMs(250); RED_SPEED = 0; BLU_SPEED = 0; DelayMs(250); DelayMs(250); // Pivot & stop //// GOOD TESTING: SPEED = 0x80 DelayMs=250 RED_DIREC = DIR_FORWARD; BLU_DIREC = DIR_REVERSE; RED_SPEED = 0x80; BLU_SPEED = 0x80; DelayMs(250); RED_SPEED = 0; BLU_SPEED = 0; // The LED flash will delay a moment before resuming forward motion break; // break out of internal while(1) loop } else if (AD_RA2() > IR_THRESHOLD) { PORTC = 0x01; // LED output indication // Stop now, then backup&stop, pivot&stop, & return to outside loop RED_SPEED = 0; BLU_SPEED = 0; DelayMs(250); DelayMs(250); // Backup & stop RED_DIREC = DIR_REVERSE; BLU_DIREC = DIR_REVERSE; uctmp = AD_RA0(); // Get the speed from the potentiometer on RA0 RED_SPEED = uctmp; BLU_SPEED = uctmp; DelayMs(250); DelayMs(250); DelayMs(250); DelayMs(250); RED_SPEED = 0; BLU_SPEED = 0; DelayMs(250); DelayMs(250); // Pivot & stop RED_DIREC = DIR_REVERSE; BLU_DIREC = DIR_FORWARD; RED_SPEED = 0x80; BLU_SPEED = 0x80; DelayMs(250); RED_SPEED = 0; BLU_SPEED = 0; // The LED flash will delay a moment before resuming forward motion break; // break out of internal while(1) loop } } } }