// target is atmega32 // // Note JTAG enabled by default. This conflicts with full use of PORTC. To // disable: (in avrdude terminal) write hfuse 0 0xd9 // // Pin Function // // PB3 / OC0 steering motor PWM positive // PD7 / OC2 steering motor PWM negative // // PD5 / OC1A drive motor PWM A // PD4 / OC1B drive motor PWM B // // PC4 relay coil DPDT reverse A // PC5 relay coil DPDT reverse B // // PD0 / RXD serial port receive // PD1 / TXD serial port transmit // // PC2 pulse to piezo rate gyro // PC3 pulse from piezo rate gyro // // AD0 steering potentiometer // AD1 Sharp IR distance sensor 1 // AD2 Sharp IR distance sensor 2 // // PC6 general purpose 1 // PC7 general purpose 2 // // actual clock speed is 1.229 MHz (on chip RC oscillator) #define F_CPU 1229000 #include #include #define UART_TXEN_BUF_GYRO 0 #define UART_TXEN_BUF_STEERING 1 #define UART_TXEN_BUF_RANGE 2 #define RECV_FRAME_BYTE 0xff #define TXEN_FRAME_BYTE 0x00 // 0x55 = 01010101 appears as ...11110101010101111... #define TEST_FRAME_BYTE 0x55 // // *** Serial protocol to microcontroller *** // // byte 0 - framing byte // 0xff - value only occurs as framing byte // // byte 1 - command // 0x03 - set duty cycle for OC0 (steering motor PWM positive) // 0x0f - set duty cycle for OC1A (drive motor PWM A) // 0xf0 - set duty cycle for OC1B (drive motor PWM B) // 0x30 - set duty cycle for OC2 (steering motor PWM negative) // 0x5e - set PC4 on/off (relay coil DPDT reverse A) // 0x6e - set PC5 on/off (relay coil DPDT reverse B) // 0x5d - set PC6 on/off // 0x6d - set PC7 on/off // 0x75 - set steering potentiometer target window low value // 0x76 - set steering potentiometer target window high value // 0x11 - set pulse width to gyro // 0x7e - stop pulse generation // 0x66 - calibration mode // // byte 2 - value or specifier // 0x00 to 0xfe - duty cycle for PWM // - delay count for pulse width // - steering potentiometer target window limit value // 0x0e - off (relay coil) // 0xee - on (relay coil) // 0xe7 - drive and steering motor plus gyro (stop pulse) // 0xe2 - drive motor (stop pulse) // 0xe4 - steering motor (stop pulse) // 0xe6 - gyro (stop pulse) // 0x60 - off (calibration mode) // 0x62 - transmit test frame (calibration mode) // // byte 3 - checksum // 0x00 to 0xfe - byte 1 + byte 2 if sum is not 0xff // - 0x00 if sum is 0xff // // Note that the checksum can have 255 different values only. At best, there // is a 1/255 probability that noise causes an undetected error. // // *** Serial protocol from microcontroller *** // // Voltages are left adjusted ADCH A/D voltage conversion for 8 bit resolution // // Each frame looks like: // // (calibration mode off) // byte 0 - pulse width from gyro // byte 1 - voltage of steering potentiometer divider (AD0 pin) // byte 2 - voltage of sharp IR sensors 1 and 2 averaged (AD1 and AD2 pins) // // Jitter accumulates so the earlier bytes are transmitted with fewer errors. // The order is deliberate and reflects importance. // // (calibration mode on) // byte 0, 1, 2 - TEST_FRAME_BYTE // uint8_t uart_recv_buf[3], // serial receive buffer data (no frame byte) uart_txen_buf[3], // serial transmit buffer data pulse_to_gyro, // delay count for pulse to gyro relay_coil_a, // relay coil controlled by PC4 pin relay_coil_b, // relay coil controlled by PC5 pin general_1, // general purpose PC6 pin general_2, // general purpose PC7 pin steering_low, // window low limit for steering pot to stop motor steering_high, // window high limit for steering pot to stop motor calibration_mode, // calibration mode runaway_counter; // when counter reaches overflows then all stop // // Configure I/O ports // void init_ports() { // serial port UBRRL = 0x19; // 2400 baud at 1.229 MHz clock, pulse width is 335-340 us UCSRB = _BV(RXEN) | _BV(TXEN); // enable receiver and transmitter UCSRC = _BV(URSEL) | _BV(UCSZ1) | _BV(UCSZ0); // 8-N-1 // OC0 timer/counter PWM output DDRB = _BV(DDB3); // PC2 gyro pulse, PC4/5 relay coil and PC6/7 general purpose DDRC = _BV(DDC2) | _BV(DDC4) | _BV(DDC5) | _BV(DDC6) | _BV(DDC7); // OC1B, OC1A, OC2 timer/counter PWM output DDRD = _BV(DDD4) | _BV(DDD5) | _BV(DDD7); // 8 bit phase correct PWM, actual frequency is 1.975 kHz TCCR0 = _BV(COM01) | _BV(WGM00) | _BV(CS00); TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10); TCCR1B = _BV(CS10); TCCR2 = _BV(COM21) | _BV(WGM20) | _BV(CS20); } // // Read byte from serial port and store in the frame buffer. // Return true if there is a complete frame, otherwise false. // uint8_t complete_recv_frame() { static uint8_t uart_recv_idx = sizeof(uart_recv_buf); uint8_t inbyte = UDR; // read in byte from serial port if (inbyte == RECV_FRAME_BYTE) // new frame detected so reset buffer index { uart_recv_idx = 0; } else if (uart_recv_idx < sizeof(uart_recv_buf)) { // store byte in frame buffer uart_recv_buf[uart_recv_idx++] = inbyte; // return true if there is a complete frame return uart_recv_idx == sizeof(uart_recv_buf); } // the frame is not complete return 0; } // // Take appropriate action based on the command and value/specifier in the // received frame from the serial port. This should only be called when there // is a complete frame available. // void process_recv_frame() { const uint8_t cmd = uart_recv_buf[0]; const uint8_t valspec = uart_recv_buf[1]; const uint8_t checksum = uart_recv_buf[2]; // should equal cmd + valspec // except when sum is 0xff // only proceed if no checksum error detected const uint8_t msgchk = cmd + valspec; if ( (msgchk == checksum) || (msgchk == 0xff && checksum == 0x00) ) { uint8_t good_command = 1; // presume the command is good switch (cmd) // command byte { // 3 case (0x03): // set duty cycle for OC0 (steering motor PWM) OCR0 = valspec; break; // 15 case (0x0f): // set duty cycle for OC1A (drive motor PWM A) OCR1A = valspec; break; // 240 case (0xf0): // set duty cycle for OC1B (drive motor PWM B) OCR1B = valspec; break; // 48 case (0x30): // set duty cycle for OC2 (steering motor PWM) OCR2 = valspec; break; // 94 case (0x5e): // set PC4 on/off (relay coil reverse A) if (valspec == 0x0e) // 14 - relay coil off { relay_coil_a = 0; } else if (valspec == 0xee) // 238 - relay coil on { relay_coil_a = 1; } break; // 110 case (0x6e): // set PC5 on/off (relay coil reverse B) if (valspec == 0x0e) // 14 - relay coil off { relay_coil_b = 0; } else if (valspec == 0xee) // 238 - relay coil on { relay_coil_b = 1; } break; // 93 case (0x5d): // set PC6 on/off if (valspec == 0x0e) // 14 - off { general_1 = 0; } else if (valspec == 0xee) // 238 - on { general_1 = 1; } break; // 109 case (0x6d): // set PC7 on/off if (valspec == 0x0e) // 14 - off { general_2 = 0; } else if (valspec == 0xee) // 238 - on { general_2 = 1; } break; // 117 case (0x75): // set steering potentiometer target window low steering_low = valspec; break; // 118 case (0x76): // set steering potentiometer target window high steering_high = valspec; break; // 17 case (0x11): // set pulse width to gyro pulse_to_gyro = valspec; break; // 126 case (0x7e): // stop pulse generation if (valspec == 0xe7) // 231 - stop all pulses { OCR0 = 0x00; OCR1A = 0x00; OCR1B = 0x00; OCR2 = 0x00; pulse_to_gyro = 0x00; } else if (valspec == 0xe2) // 226 - stop drive motors only { OCR1A = 0x00; OCR1B = 0x00; } else if (valspec == 0xe4) // 228 - stop steering motor only { OCR0 = 0x00; OCR2 = 0x00; } else if (valspec == 0xe6) // 230 - stop pulses to gyro only { pulse_to_gyro = 0x00; } break; // 102 case (0x66): // calibration mode if (valspec == 0x60) // 96 - off { calibration_mode = 0; } else if (valspec == 0x62) // 98 - transmit test frame { calibration_mode = 1; } break; // unrecognized command default: good_command = 0; } // only reset the runaway counter if the command was good if (good_command) { runaway_counter = 0; } } } // // Process serial port receive and handle commands in input frames // void uart_recv_frame() { if (complete_recv_frame()) { process_recv_frame(); } // check if runaway detected - no good command frames after 256 bytes read // from serial port if (runaway_counter++ == 0xff) { // stop all pulse generation OCR0 = 0x00; OCR1A = 0x00; OCR1B = 0x00; OCR2 = 0x00; pulse_to_gyro = 0x00; } } // // Transmit one byte from a complete frame over the serial port // void uart_txen_frame() { static uint8_t uart_txen_idx = sizeof(uart_txen_buf); if (uart_txen_idx < sizeof(uart_txen_buf)) { if (calibration_mode) { UDR = TEST_FRAME_BYTE; // transmit test byte } else { UDR = uart_txen_buf[uart_txen_idx]; // transmit byte from buffer } uart_txen_idx++; // next byte } else { // gyro pulse width reading is unpredictable while the AD conversions // occur on every pass so the frame is complete when the gyro pulse is // available (as thee AD conversions are always available) if (uart_txen_buf[UART_TXEN_BUF_GYRO]) { uart_txen_idx = 0; // transmit the frame } } } // // Generate software delay loop pulses to the steering motor and gyro. // Output pulses from the gyro are measured and stored. // void gen_pulses() { // pin outputs go high uint8_t portc = 0x00; if (pulse_to_gyro) { portc |= _BV(DDC2); } if (relay_coil_a) { portc |= _BV(DDC4); } if (relay_coil_b) { portc |= _BV(DDC5); } if (general_1) { portc |= _BV(DDC6); } if (general_2) { portc |= _BV(DDC7); } PORTC = portc; uint8_t before_gyro_pulse = 0, after_gyro_pulse = 0, gyro_count = 0, i = 0, doLoop = 1; // main loop to turn pins off and count gyro output pulse width while (doLoop) { // pin outputs go low if (i == pulse_to_gyro) { portc &= (uint8_t) ~ _BV(DDC2); PORTC = portc; } // is there a pulse from the gyro? if (PINC & _BV(PINC3)) { // increment counter of the gyro's output pulse width gyro_count++; } // there is no pulse else { // the gyro pulse will arrive later, the starting edge is not sliced off if (gyro_count == 0) { before_gyro_pulse = 1; } // the gyro pulse has passed else { after_gyro_pulse = 1; } } // increment index if (++i == 0) { doLoop = 0; } } // partial, sliced gyro pulses are not counted if (before_gyro_pulse && after_gyro_pulse) { // store the reading for later transmission uart_txen_buf[UART_TXEN_BUF_GYRO] = gyro_count; } } // // Read voltages on pins AD0, AD1, AD2 and store in transmit buffer // void ad_conversion() { // steering potentiometer ADMUX = _BV(REFS0) | _BV(ADLAR); // AD0 pin ADCSRA = _BV(ADEN) | _BV(ADSC); // start conversion while (ADCSRA & _BV(ADSC)) ; // wait for result uint8_t steering_pot = ADCH; // 8 bit resolution uart_txen_buf[UART_TXEN_BUF_STEERING] = steering_pot; // sharp IR rangefinder 1 ADMUX = _BV(REFS0) | _BV(ADLAR) | _BV(MUX0); // AD1 pin ADCSRA = _BV(ADEN) | _BV(ADSC); // start conversion while (ADCSRA & _BV(ADSC)) ; // wait for result uint8_t sensor1 = ADCH; // 8 bit resolution // sharp IR rangefinder 2 ADMUX = _BV(REFS0) | _BV(ADLAR) | _BV(MUX1); // AD2 pin ADCSRA = _BV(ADEN) | _BV(ADSC); // start conversion while (ADCSRA & _BV(ADSC)) ; // wait for result uint8_t sensor2 = ADCH; // 8 bit resolution // average range reading int sensor_sum = sensor1 + sensor2; uart_txen_buf[UART_TXEN_BUF_RANGE] = sensor_sum / 2; // average value // check for steering potentiometer inside of target window if ( (steering_pot >= steering_low) && (steering_pot <= steering_high) ) { // stop the steering motor OCR0 = 0x00; OCR2 = 0x00; } } // // Main entry point // int main() { // configure I/O ports init_ports(); // loop forever while (1) { // serial port receive and process command frames uart_recv_frame(); // serial port transmit status frames uart_txen_frame(); // generate PC4/5 and gyro pulses gen_pulses(); // analog-digital conversions ad_conversion(); } }