Servo Motor and ADC Synchronization Issue in AVR C Code

105 Views Asked by At

I'm working on an AVR ATMega32a microcontroller-based project where I'm using a servo motor to rotate from 0 to 90 degrees while capturing ADC values at each degree of rotation. I aim to synchronize the servo motor's rotation with the ADC value capture, ensuring that I receive the corresponding ADC value for each degree of rotation before proceeding to the next degree.

Context:

Microcontroller: I'm using an AVR microcontroller, ATmega32A. MG995 Servo Motor: The servo motor is controlled via PWM to rotate from 0 to 90 degrees. ADC Configuration: I have configured the ADC to read values from a photodiode sensor. Connections: The servo motor and the photodiode sensor are connected to the microcontroller.

The problem I'm facing is that the servo motor appears to rotate 90 degrees before the ADC values are transmitted for all degrees. Ideally, I expect the motor to rotate to each degree incrementally and wait for the corresponding ADC value before proceeding to the next degree.

Here's a simplified version of the relevant parts of my C code:

// Function to transmit a byte via UART
void UART_TxChar(char ch) {
    while (! (UCSRA & (1 << UDRE))); /* Wait for empty transmit buffer */
    UDR = ch;
}

// Function to send a string via UART
void UART_SendString(char *str) {
    unsigned char j = 0;
    while (str[j] != 0) /* Send string up to null */
    {
        UART_TxChar(str[j]);
        j++;
    }
}
...
// Function to rotate the servo to a specified degree
void RotateServo(uint16_t degree) {
    OCR1A = (degree * 125) / 9 + 175; // Calculate the OCR1A value for the specified degree
    _delay_ms(500); // Wait for the servo to reach the desired position
}

int main() {
    ...

    while (1) {
        for (int degree = 0; degree <= 90; degree++) {
            RotateServo(degree);

            // Read the ADC value from channel 0 (connected to photodiode)
            adc_value = adc_read(0);

            // Print the ADC value to the serial terminal
            sprintf(buffer, "Degree: %d, ADC Value: %d \n", degree, adc_value);
            UART_SendString(buffer);

            // Wait for a moment before moving to the next degree
            _delay_ms(1000);
        }

        _delay_ms(2000); // Wait for 2 seconds before reversing

        for (int degree = 90; degree >= 0; degree--) {
            RotateServo(degree);

            // Read the ADC value from channel 0 (connected to photodiode)
            adc_value = adc_read(0);

            // Print the ADC value to the serial terminal
            sprintf(buffer, "Degree: %d, ADC Value: %d \n", degree, adc_value);
            UART_SendString(buffer);

            // Wait for a moment before moving to the next degree
            _delay_ms(1000);
        }

        _delay_ms(1000); // Wait for 1 second before restarting the loop
    }

    return 0;
}

I have attempted to adjust the delay between servo motor movements and ADC value captures, but this hasn't resolved the issue.

My expectation is to achieve synchronized rotation of the servo motor and the capture of ADC values, degree by degree, ensuring that the motor doesn't move to the next degree until the ADC value for the current degree is obtained.

  • Is there a way to synchronize the servo motor rotation with ADC value capture for each degree?
  • Are there any potential reasons for the delay in ADC value transmission?
  • Are there adjustments needed in the code or hardware setup to achieve synchronization?

I would appreciate any insights, advice, or solutions that can help me address this synchronization issue. Thank you for your assistance.

Edit: Finally I figured out the mistake

    // Function to rotate the servo to a specified degree
void RotateServo(uint16_t degree) {
    OCR1A = (degree * 125) / 90 + 175; // Calculate the OCR1A value for the specified degree
    _delay_ms(500); // Wait for the servo to reach the desired position
}

The only mistake I made was not adding 90 for the specified degree which made my motor rotate 10x speed in one turn. My bad

Thank you so much to everyone who helped me in numerous ways.

2

There are 2 best solutions below

5
On

As things stand now, everything in your program depends on anything else. Both UART transmission and sprintf are very time consuming. Whereas an ADC cycle is extremely fast in comparison to all that, let alone the massive delays everywhere.

So step #1 is to convert all of this from "hobbyist hell" to an actual real-time system, which can do multiple things at the same time. That means get rid of all the evil _delay_ms and start using hardware peripheral timers. You should have something handling the ADC, something handling the servo and something handling the console stuff/UART and these 3 things shouldn't have tight coupling timing dependencies vs each other.

Each driver could have a callback triggering every 1ms or something, then deciding if it is time to act. Is the UART available or busy - if busy, then obviously we shouldn't sit around and twiddle thumbs in a busy-wait but leave the timer callback and let the rest of the program do meaningful stuff instead while we wait. Same with servo and ADC.

Depending on how your ADC driver works and how the ADC is set up, it might return the last value read, or it might actually busy-wait poll the ADC to finish (which would be bad too). Sometimes you want the ADC to require a manual kick to initiate each conversion, in other situations you want to use a "continuous conversion" feature to leave the ADC running and repeatedly update its data register and then you just grab the latest value. I can't say what suits your application best.

0
On

Your problem probably has nothing to do with the ADC, but rather the code that initializes the timer you used to generate the PWM pulse, or more precisely - the lack of it.

Additionally, its unclear whether or not you are correctly converting the 0-90 number into a correct OCR1A value which corresponds to the servo's specs (typical servos have a period of 20ms, where the first 500-2500us are the high pulse) I see no #define for your CPU frequency, no code which defines output pins for the pwm outputs, you basically have no code that controls the most important part of moving the motor correctly. I suggest diving into the datasheet section for the timer counter 1 (page 93).