Blender, Arduino and two servos

Hello everybody,

I´m working on a robotic school project with my students. Everything works fine until two weeks ago. I want to control two servo motors via blender as seen on many youtube-videos, but I just can´t get it run. It should be realy simple but for me it´s not possible I think:

  • two cubes rotating on the x-axis in blender(animated)
  • the two servos should do simply the same

I use Blender 2.63, Python 3.2 and pyserial 2.6.

I found exactly what I need on the web, but this stuff is for much older versions.

Is there anybody out there who can help me with the blender and arduino codes?

Thank you very much!

Where are you stuck? There was a fairly recent post in the forums where somebody had some something very similar over ZigBee (I think it was ZigBee).

It the blender script throwing errors? Are you getting communication errors? Is the sketch loading on the Arduino correctly? For that matter, what Arduino board do you have? Do you have a schematic for how the servos are wired up?

With an integration project like this, there are lots of places where the error could cause the project to fail. To be any help we would need all the source (Python + Arduino code) and your schematic.

Are you familiar with all these pieces? i.e. Are you comfortable with Python, Blender, Arduino/AVR uC, and electronics? If not, which areas are new to you?

Hi Kostoria,

thank you for answering. I´m pretty good in blender itself, also in electronics, but I have very less experience in python and coding itself. I got the arduino run for controlling it with a potentiometer and I´ve tried the following codes:

Blenderscript:

import bpy
import math
from math import degrees
import serial
ser = serial.Serial(“COM6”,9600,timeout=1)
data = ‘’
Servoa = 9
Servob = 10
eulx = bpy.data.objects[‘Cube’].rotation_euler
x = degrees(eul.x)
euly = bpy.data.objects[‘Cube1’].rotation_euler
y = degrees(eul.x)
data += bytes(Servoa) + bytes(int(x))
data += bytes(servob) + bytes(int(y))
ser.write(data)
ser.close()

Arduinoscript:

#include <Servo.h>

Servo A;
Servo B;
void setup()
{
A.attach(9);
B.attach(10);
Serial.begin(9600);

A.write(0);
B.write(0);
}
int num;
int angle;
void loop()
{
while(Serial.available() == 0);
num = Serial.read();

while(Serial.available() == 0);
angle = Serial.read();

if (num == 9)
A.write(angle);
else if (num == 10)
B.write(angle);
}

The serial connection should work, the “rx” and the “tx”-lights go on for a moment when I run the script. I hope the codes are not too foolish, but as I said, I´ve less experience in that.

Thank you again.

Ok, if you have the servos working on a potentiometer then the servos are wired up correctly, not drawing too much current, and sketches are uploading to the board correctly. That helps narrow some things down.

The idea behind the code looks perfectly fine. This is not controlling a nuclear reactor so super-simple is the best way to go. However, I think there are a few 'gotcha’s in the Python script.

  1. Arduino is officially a “prototyping” board. One of the reasons that it is so easy to upload new sketches to the Arduino board is that it resets whenever a serial connection is made to it. That means every time you run your python script and open the COM port, your board resets and runs the “setup()” function again. That is probably the flashing Tx/Rx lights you are seeing on the board.

  2. Serial communication is slow. :frowning: The time it takes for the serial connection to actually open and be ready for reading/writing is probably longer than it is taking for you to compose your angle commands and send them out. Your writes are probably getting dropped completely.

  3. I have not used the ‘bytes’ data type in python but I just read the docs and played with them a bit and I think that your constructor use is not doing what you think it is doing.
    http://docs.python.org/3/library/stdtypes.html#binaryseq

When you do this:


bytes(servoa)

It is the same as this:


bytes(9)

Which the docs say creates an array that is 9 bytes long filled with zeros. I tested and this is what happens. If you want a single byte with the value 9 then you need to do something like this:


bytes( [9] )
bytes( b"\x09" )

So you might want to compose your command sends more like this:


data = bytes( [Servoa, int(x), Servob, int(y) ] )

Here are some suggestions:

  1. Fix the data encoding so you are sending the correct commands.

  2. Try to account for the fact that serial communication opens slowly. You have some options. Check the Python docs for the serial library and see if there is a way to test the connection to see if it is open for reading/writing. If there is then you can just loop on this test until it is read then send your data. Another option is to have the board send a character once/sec and your python code can listen for that. You open the COM then wait to receive a ‘heatbeat’ byte then you can write your command. The downside with that is you have to keep consuming the heartbeat bytes or they might overflow your communication buffer.

  3. The fact that each COM connection resets the board is harder. It is possible to disable some of the Arduino hardware to prevent this, but that also makes it much harder to upload new sketches.
    When I ran into this problem, I had a webserver controlling 2 servos though the Arduino so I just had the webserver create the COM connection once when it started up then I reused that connection for each later call. This also helped with the slow startup for serial communication because the pipe was always open.

I’m not that familiar with how to store persistent variable in Blender across executions. I’m sure there is a way to do it but I’ve never hit that use case myself so I have not tried. Maybe you can attach the serial instance as a property on an Empty or something. Or maybe Blender has a global namespace that you can stuff it in.

If you want to test out the theory that the connection is slow to open and that the commands were not composed correctly then you can try this:
-Open the serial port connection
-Do something to get Blender to pause for 1-2 seconds. Maybe time.sleep(2) but I’m not sure if that would hang the serial connection as well.

  • Go into a loop where you write your commands to the board 100 times.

That might show if you are hitting some of the harder problems I pointed out before you go research that.

Wow, your explanations are very good! And as much as I can test it, you are absolutely right. I tested a code for the serial connection for just one servo:

import bpy
import math
from math import degrees
import serial
ser = serial.Serial(‘COM6’,9600,timeout=1)
def my_handler(scene):
eul = bpy.data.objects[‘Cube’].rotation_euler
s = degrees(eul.x)
data = bytes(int(s))
ser.write(data)
bpy.app.handlers.frame_change_post.append(my_handler)

Now the RX lights up all the time the animation runs. The servo isn´t moving, but the blender animation slows down to about 2 frames/sec. So that´s possible not the goal :slight_smile:

Sadly, the codes above are the highest end of my knowledge and I´m definitely not able to do the things you mentioned.

All new research on the web didn´t push me forward, but I found a guy who did what I look for, but with stepper motors. Very great work! This solution is not possible for me, because the motors are much to big for the robotic model I made with the kids, but to test it, I put all the stuff together and the animation runs on full speed (up to 50 frames/sec) and the motor moves very smoothly. So it is definitelly possible!

You can take a look at it on: eibriel dot com and camera motion control with blender

Maybe it is possible to change this code to my needs?

First a tip, when you are posting source code you want to wrap it in some “CODE” tags. When editing a message hit the “#” icon. This will preserve your whitespaces which are critical for understanding what a Python program is doing.

It looks like you are almost there. I think your only error is:


data = bytes(int(s))

That is still creating a byte array that is “s” bytes long and filled with 0x00. You want:


data = bytes( [ int(s) ] )

Those “” mean you are passing in a list of numbers that is only 1 number long. I think that if you change that one line you will see some more success. Your handler is doing less work than his handle so I think your slow down is the fact that he is sending a single byte per frame to the board and you are sending hundreds of bytes per frame.

I agree. As im writing the simpler code I forgot to write this line as you explained. I will test it tomorrow morning!

Thank you!

HEY :slight_smile: You realy pushed me a step forward. Thank you very much!

Unbelieveable that these two “edgy” brackets make the difference. I´m now able to move ONE servo as I wish.

For the last few hours I tried to rewrite the code for TWO servos, but without any progress. Maybe you have a idea how to solve this problem. But by now, I realy thank you for your time and your knowledge you shared with me.

Here are the working codes for ONE servo:

import bpy
import math
from math import degrees
import serial
ser = serial.Serial('COM6',9600,timeout=1)
def my_handler(scene):
 eul = bpy.data.objects['Cube'].rotation_euler
 s = degrees(eul.x)
 data = bytes([int(s)])
 ser.write(data)
bpy.app.handlers.frame_change_post.append(my_handler)

And the code for the arduino:

#include &lt;Servo.h&gt;
Servo servo;
void setup()
{
  servo.attach(9);
  
  Serial.begin(9600);
  
  servo.write(90);
}
int angle;
void loop()
{  
  while(Serial.available() == 0);
  angle = Serial.read();
  
  servo.write(angle);
}


I think you are there. I really wish all my uC’s and breadboards were not tied up in other projects right now because I wish I could just test this. Anyway, I think that your original sketch should work fine and we just have to put all the pices of the python script together.

The hardware/software setup should be that the Euler X value on “Cube” will control the servo on pin 9. The Euler X value on “Cube1” will control the servo on pin 10.

Original Arduino sketch:


#include &lt;Servo.h&gt;

Servo A;
Servo B;
void setup()
{
  A.attach(9);
  B.attach(10);
  Serial.begin(9600);

  A.write(0);
  B.write(0);
}
int num;
int angle;
void loop()
{
  while(Serial.available() == 0);
  num = Serial.read();

  while(Serial.available() == 0);
  angle = Serial.read();

  if (num == 9)
    A.write(angle);
  else if (num == 10)
    B.write(angle);
}

Combined Python script:


import bpy
import math
from math import degrees
import serial

# Create a persistent connection to COM port 6
ser = serial.Serial('COM6',9600,timeout=1)

def my_handler(scene): 
  """Handler that get's the Euler X rotation from two cubes and passes this to servos 9 and 10 though the serial port.""" 

  # Get the Euler x rotation in degrees.  The rotation for "Cube" will be applied to servo on pin 9
  servoAngle9 = degrees(bpy.data.objects['Cube'].rotation_euler.x)
 
  # Get the Euler x rotation in degrees.  The rotation for "Cube1" will be applied to the servo on pin 10
  servoAngle10 = degrees(bpy.data.objects['Cube1'].rotation_euler.x)

  # Convert these values into a byte array.
  data = bytes( [9, int(servoAngle9), 10, int(servoAngle10)] )
 
  # Write the commands to the serial port.
  ser.write(data)

# Register the handler to be called once the frame has changed.
bpy.app.handlers.frame_change_post.append(my_handler)

What will be the code to control two servos in python??