/* 2piLineFollower.c - Mark Moran 2014 Redistribution and use in source and binary forms, with or without modification, are permitted freely. Inspired by Pololu's QTRSensors & 3pi Line Follower apps written by Ben Schmidel et al. Written for ATmega328p running about 8MHz with internal RC time source. (Should work with any AVR chip with 16bit & 8bit timers and enough pins) Will work with external clock source but may need to change clock scale prefactor. Drives two motors connected to SN754410 quad H-bridge. Detects lines using Pololu QTR-8RC chip (with sensors 7 & 8 removed) */ #include #include /*************** MOTOR FUNCTIONS ***************/ #define MOTORPORT PORTD #define MOTORDDR DDRD #define PIN1A PD3 #define PIN2A PD2 #define PIN3A PD7 #define PIN4A PD4 #define PIN12EN PD5 #define PIN34EN PD6 #define PIN12CNTR OCR0B #define PIN34CNTR OCR0A #define LEFTA PIN4A #define LEFTB PIN3A #define LEFTON PIN34EN #define LEFTCNTR PIN34CNTR #define LEFTBIT COM0A1 #define RIGHTA PIN1A #define RIGHTB PIN2A #define RIGHTON PIN12EN #define RIGHTCNTR PIN12CNTR #define RIGHTBIT COM0B1 static inline void initMotors(void) { TCCR0A = (1< 255) speed = 255; if (speed == 0) TCCR0A &= ~(1< 255) speed = 255; if (speed == 0) TCCR0A &= ~(1< QTRmax[i]) QTRmax[i] = QTRtime[i]; if (QTRtime[i] < QTRmin[i]) QTRmin[i] = QTRtime[i]; } } } void readCalibrated(void) { uint8_t i; uint16_t range; readQTRs(FAST); for (i = 0; i < QTRCNT; i++) { // normalize readings 0-1000 relative to min & max if (QTRtime[i] < QTRmin[i]) // check if reading is within calibrated reading QTRtime[i] = 0; else if (QTRtime[i] > QTRmax[i]) QTRtime[i] = 1000; else { range = QTRmax[i] - QTRmin[i]; if (!range) // avoid div by zero if min & max are equal (broken sensor) QTRtime[i] = 0; else QTRtime[i] = ((int32_t)(QTRtime[i]) - QTRmin[i]) * 1000 / range; } } } uint16_t readLine(void) { uint8_t i, onLine = 0; uint32_t avg; // weighted total, long before division uint16_t sum; // total values (used for division) static uint16_t lastValue = 0; // assume line is initially all the way left (arbitrary) readCalibrated(); avg = 0; sum = 0; for (i = 0; i < QTRCNT; i++) { // if following white line, set QTRtime[i] = 1000 - QTRtime[i] if (QTRtime[i] > 50) { // only average in values that are above a noise threshold avg += (uint32_t)(QTRtime[i]) * (i * 1000); sum += QTRtime[i]; if (QTRtime[i] > 200) // see if we're above the line onLine = 1; } } if (!onLine) { if (lastValue < ((QTRCNT-1)*1000/2)) // if last read right of center, return 0 (assume veered right) return 0; else // otherwise return max (assume veered left) return (QTRCNT-1)*1000; } lastValue = avg/sum; // no chance of div by zero since onLine was true return lastValue; } void spinAndCalibrate(void) { uint8_t i; for (i = 0; i < QTRCNT; i++) { // reset minimums and maximums QTRmax[i] = 0; QTRmin[i] = MAXTICKS; } for(i = 0; i < 60; i++) { if ((i < 15) || (i >= 45)) setSpeed(40, -40); else setSpeed(-40, 40); calibrateQTRs(); _delay_ms(5); } setSpeed(0,0); } #define MAX 60 int main(void) { int16_t proportional, derivative, lastProportional = 0, powerDiff; uint16_t position; uint32_t integral = 0; uint8_t i; initMotors(); initQTRs(); _delay_ms(1000); // pull hand away from switch spinAndCalibrate(); while (1) { position = readLine(); // 0 = far right, 5000 = far left proportional = ((int16_t)position) - 2500; // "proportional" term should be 0 when we on the line derivative = proportional - lastProportional; // change of position integral += proportional; // sum of positions lastProportional = proportional; // remember the last position powerDiff = proportional/20 + integral/10000 + derivative*3/2; if (powerDiff > MAX) powerDiff = MAX; else if (powerDiff < -MAX) powerDiff = -MAX; if (powerDiff < 0) setSpeed(MAX, MAX+powerDiff); else setSpeed(MAX-powerDiff, MAX); } return 0; }