8servo controller using single 74hc595

I gave it a try tonight, since I always wandered whether it can be done. Control 8 servos over two wires, that's pretty attractive.

So the answer is yes, it can... and no it can not... Why?

Pros:

- It worked. I managed to control 8 servos independently (they are indpendently postionable but can not be individually switched off - which would be nice in some circumstances (power saving))

- On Atmega 168 @ 16 Hz I achieved resolution of 125 steps per 1 ms and since I managed control the servo in the range of almost 0.5 - 2.5 ms I get ~ 250 steps per sth around 180 degrees

- It indeed uses only 2 wires to control 8 servos

- the interface is very simple - you just call a procedure to set a servo position

- the circuit is dead simple also - it uses a single 74HC595

Cons:

- the handing of the add-on circuit on the microcontroller side is quite time-consuming. The interrupt routine gets called every 128 clocks, although mostly it only increases the counter, it sorta looks like the processor is busy some 50% of its time

- that asks for an implementation using a stand-alone microcontroller (sth like Attiny 2313) to leave the main unit free to do what's it supposed to do, but then you don't need the 595 at all - just one attiny would do, and you'd need a quarz resonator (20 or maybe 24 MHz overcocked) bunch of caps and at least 2 lines for data transmission and a protocol to controll the stuff.

So... I don't know... What do you think? Which way to go next?

Photo session:

top - just a single chip and pin headers

 

bottom:

red +5v, blue 0v, yellow - clock, brown = data, black = reset hooked up to 5v

testbed: homebrew arduino used as dev.board programmed & debugged over ISP+debugwire using avrdragon

I also hooked it up to LEDs on my Attiny2313 proto board which helped a lot when debugging (while debuggin I slowed down the counter 64 times to see whether all shifts ok)

** Funny, I was working on a**

Funny, I was working on a 16 servo controller using two 595’s. It usd two wires for serial comm. + one wire per 595 for the select line. I ran into some timing problems, so I put the project on hold. I’ll continue the project, when I get some time, but I don’t think that I will be using 595’s. I like the fact that you only use 2 wires + 2 per 595, but I think that precision timing is a little difficult when using the serial protocol.

Since you only use two wires, serial clock + serial data, I suspect that you have tied the latch pin to +5v (or is it active low? can’t remember…), so data goes directly to the output pins? Isn’t it a problem, that first bits are shifted through the other outputs, so the bit7-servo will see a lot of “noise” from the data for the other 7 servos shifting by? My strategy was to use the extra pin for the latch pin, so I wouldn’t disturb the servos when the data was shifted in.

By the way, if you leave the bit for a servo 0 all the time and use the extra pin for the latching, then the servo will power down, becuase it will never see a high bit.

These chips are suited for

These chips are suited for really fast things. I mean relly fast. Their maximum clock speed is 100 MHz. The task i’ve been trying to solve is not very time-consuming in principle. The processor just hast to wait for the right time to output clock pulse. The main problem with CPU usage I have results from that I coded it in C and in C without any tweaks a lot of time is wasted in compiler-generated ISR prologue. If I have say 128 clocks between each ISR call and 64 of that is wasted by compiler for pushing / poping registers on stack. If i tweak it, I’ll probably have something of any use. But when I woke up It occured to me I can simpify the code a bit and let the hardware handle the timing alltogether. So I guess I’ll give it another try tonight.

jka - I did’t do it the way

jka - I did’t do it the way you did … to keep you thinking. I’ll showw the code here after I clean it up a bit and after I try out one more approach which occured to me this morning. It looks promising.

Anyway precission timing should not be a problem since microcontrollers are meant for precisely timed stuff (consider an OSD circuit /there are some done with AVRs/ - the timing is pretty important there - yet it )

I tried to quickly change

I tried to quickly change the code and now the ISR is called less frequently (once per 16 000 clock cycles in case of 1ms pulse) so 60 wasted cycles in prologue/epilogue is not an issue now. It was just a basic test - I still need to check out everything carefully - but this has to wait

EDIT

Now I think I’ve nailed it down. I guess it The resolution also improved now it is 2000 per ms.

 

So here it goesDa diagram

So here it goes

Da diagram

8servos.gif

Da code


#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#define CLOCKPIN 7 // define your pins here
#define DATAPIN 6
#define CDPORT PORTD // and port
#define CDDDR DDRD

#define CLOCK_HIGH CDPORT |= (1<<CLOCKPIN) // some macros to make things clean and tidy...
#define CLOCK_LOW CDPORT &= ~(1<<CLOCKPIN) // ...further on
#define DATA_HIGH CDPORT |= (1<<DATAPIN)
#define DATA_LOW CDPORT &= ~(1<<DATAPIN)

#define MS 2000 // 1 ms lenght in Timer1 cycles
#define PULSETRAIN 22*MS // total lenght of the overall pulsetrain - may be tweaked depending
// on the range servos will move - should be a couple of ms grater
// than 8 times maximum indiv. servo pulse lenght
// i.e. for 8x 2,5 ms pulses you get 22

unsigned int servopos[8], sum, tail; // that's what's being used
volatile unsigned char servo; // this one changes inside the ISR


void init_ports( void )
{
CDDDR |= (1<<CLOCKPIN) | (1<<DATAPIN);
CDPORT &= ~((1<<CLOCKPIN) | (1<<DATAPIN));
}

void init_timers( void )
{
TCCR1B = (0<<CS12) | (1<<CS11) | (0<<CS10); // F_CPU/8
OCR1A = MS; // delay 1 ms before first OCR

TIMSK1 |= (1<<OCIE1A); // Enable output compere interrupt
sei(); // Enable interrupts
}

SIGNAL( SIG_OUTPUT_COMPARE1A )
{
if( !servo ) // if this is the first servo
{
DATA_HIGH; // feed pulse into the shift register
CLOCK_HIGH; // tick to shift the pulse inside it
CLOCK_LOW; // the pulse will appear on physical output after next clock tick
DATA_LOW;
}

CLOCK_HIGH; // for each servo not only the first one, tick to shift
CLOCK_LOW;

if( servo == 8) // if all done with all servos, wait for the rest of the 22 ms (or whatever)
// pulsetrain period
{
OCR1A += tail; // get back after the tail of the pulse train ends
servo = 0; // start with the first servo on the next occassion
}
else
{
OCR1A += servopos[servo]; // keep the pulse on each ouptut for how long it's necessary
servo++; // next servo next time
}
}

void servo_initialize( void ) // pretty self-explanatory
{
unsigned char i;

sum = 0;
for( i=0; i<8; i++ )
{
servopos[i] = MS; // 1 ms
sum+=servopos[i];
}
tail = PULSETRAIN - sum; // 20ms - all pulses

servo = 0;


CLOCK_LOW; // start the clock with low

DATA_HIGH; // output 1 to data input

CLOCK_HIGH; // shift it into the register
CLOCK_LOW;

DATA_LOW; // nothing more to feed
}

// use this procedure to set individual servos to positions
// as it keeps track of the tail variable
void set_servo( unsigned char servo, unsigned int pos )
{
// sum holds the lenght of all 8 servo pulses
sum -= servopos[servo]; // so first subtract the old length
sum += pos; // then add the new one
servopos[servo] = pos; // store where it belongs
tail = PULSETRAIN - sum; // update tail variable
}


void main( void ) // demo usage
{
int i;
unsigned int val;
int dval = 1;

init_ports(); // initialize
servo_initialize();
init_timers();

val = MS+MS/2; // center position 1.5 ms
while( 1 )
{
// the DEMO
for( i=0; i<8; i++ ) // move all servos
set_servo( i, val );

val += dval;

if( val > MS+MS+MS/2 ) // up until 2.5 ms then sweep down
dval = -1; // change direction

if( val < MS*5/8 ) // down to 0.625 ms then sweep up
dval = 1; // change direction

_delay_ms( 20 ); // delay for a while
}
}

 


Da explanation

 

What i do is

- I reall do not need a shift register with a latch here - so I tied latch and data clocks together, which according to spec. sheet keeps the data clock one tick ahead of the latch

- I feed only one high bit to the shift register and keep it at corresponding servo's output for the duration of the servo pulse lengh, then I shift the pulse onto another servo and repeat

- after all servos are done, I wait a bit so that the whole cycle is ca. 20 - 22 ms and each servo gets its data roughly with that period

I guess this is how it is done in RC receivers

 

perfect

great

 

I'll have to convert it into arduino library now

 

 

Did you make the library for the arduino?
I need to use in with an esp8266.
Do you know if it works?