SPI and servo.writeMicroseconds

Hello everyone,

I have successfully build a quadcopter using Arduino with altitude hold using SRF 05 ultrasonic sensor. Now, I am using an ADNS3080 optical flow sensor for position hold, however, it is quite weird to me that the quadcopter becomes EXTREMLY UNSTABLE when flying and reading the data from the optical flow (with no horizontal control yet).

Previously, I have the same phenomenon with the ultrasonic and find out that it is due to the delayMicroseconds that makes my quadcopter unstable. But now, I have implement a new delay function when reading through SPI interface.

void MyDelay(unsigned long MicroSeconds){
long start_micro = micros();
unsigned long current_micro = micros();
while(current_micro-start_micro < MicroSeconds){
current_micro = micros();
};
}

The phenomenon is like one motor is being cut down the power.

You've successfully reinvented the wheel except yours has a flat tyre. There is already a delayMicroseconds() function available.

You should not use blocking delays. Can you show us the code which calls this MyDelay() please?

Use [ code ] tags next time.

Do not use delay (or delayMicros) or any other blocking function! See blink without delay. Your code needs a complete rewrite.

Mark

The reason why I do not use the delayMicroseconds is that previously, when I do an altitude hold with my quadcopter, it seems like the delayMicroseconds() function call during measuring the altitude from SRF05 will make the Servo.writeMicroseconds() malfunction. For example, if I write :

void loop(){
delayMicroseconds(50);
Servo.writeMicroseconds(1000);

Then, as I guess, the pulse will actually becomes different from 1000 (1050 maybe, I do not have oscilloscope at my home). I actually overcome this by using writeMicroseconds() on the trigger pin . I think that the delayMicroseconds() use the Timer which has higher priority than the Timer used for writeMicroseconds().

Thus I have to rewrite into MyDelay() as shown before.

So why I cannot use blocking delay for my quadcopter ? My looptime for the quadcopter is about 10-12ms.

For your reference, here is the code that I use MyDelay():

byte read_register_ADNS3080 (byte address)
{
  byte result, stat;

  backup_spi_settings_ADNS3080();
  
  digitalWrite(SS, LOW);               // select  
  stat = SPI.transfer(address);        // send the register address
  //Serial.println(stat,HEX);
  MyDelay(50);   
  
  result = SPI.transfer(0x00);         // send a value of 0 to read the first byte returned
  MyDelay(50);   
  digitalWrite(SS, HIGH);              // de-select
  
  restore_spi_settings_ADNS3080();
  
  return result;
}

byte backup_spi_settings_ADNS3080()
{
  orig_spi_settings_spcr = SPCR & (DORD | CPOL | CPHA);  // store current spi values
  orig_spi_settings_spsr = SPSR & SPI2X;
  
  SPI.setBitOrder(MSBFIRST);                             // set the values that we need
  SPI.setDataMode(SPI_MODE3);
  SPI.setClockDivider(SPI_CLOCK_DIV8);                   // sensor running at 2Mhz - maximum speed
  return orig_spi_settings_spcr;
}
byte restore_spi_settings_ADNS3080()
{
  byte temp;
   
  temp = SPSR;                         // restore SPSR
  temp &= ~SPI2X;
  temp |= orig_spi_settings_spsr;
  SPSR = temp;
    
  temp = SPCR;                         // restore SPCR
  temp &= ~(DORD | CPOL | CPHA);       // zero out the important bits
  temp |= orig_spi_settings_spcr;      // restore important bits
  SPCR = temp;

  return temp;
}

Well, by using delayMicroseconds() twice per byte transferred, you've pissed away 10% of your loop time budget. If you do too much of that, you will exceed the time budget and the quadcopter will become unstable. Just like you observed.

From a quick perusal of the datasheet, that chip doesn't need you to add subjective-weeks of time between the SPI bytes. It does show 50us in a couple of places may be required.

Can you find something more productive for your Arduino to do while it's waiting the requisite 50us?

GET RID OF THE DELAYS.

Mark

It's not that simple. The datasheet for the sensor says you must wait 50us between reading and writing. If this time period can fit into the time budget available, then there's no reason not to use a delay. If it can't, then the programming to allow this wait period to be filled with something useful is NOT easy.

We are only talking about a 20th of a millisecond delay.

truongbuu:

 void MyDelay(unsigned long MicroSeconds){

long start_micro = micros();
 unsigned long current_micro = micros();
 while(current_micro-start_micro < MicroSeconds){
   current_micro = micros();
 };
}

This code is functionally identical to the delayMicroseconds() function because of the use of WHILE to make it block until the interval is complete.

Look at how millis() is used to manage timing without blocking in Several Things at a Time. You can use exactly the same approach with micros().

I agree with @holmes4.

At the very least, getting rid of the delays is the only way to prove whether they are the cause of the problem.

If you need a 50µsec gap between two successive actions maybe a reorganization of the flow of the program would provide that without any need for an interval.

...R

OK, I think I will get rid of those delay now. Sure this will take away my beautiful loop flows, but let's see if that what I can get :).

Actually, one of the things I didn't give up my delay is that in the reference code from Ardupilot, they use function

hal.scheduler->delay_microseconds(50);

Originally, I did not realize that it was some kind of multi-threading in their HAL library. So that's may be the different between the Arduino delay function and the HAL delay function.

Thank you for your inputs :slight_smile:

truongbuu:
Sure this will take away my beautiful loop flows,

The end result should be an even more beautiful flow.

Have a look at Planning and Implementing a Program

...R