does anyone know, how to send larger numbers than 256 with py serial?
I created a code that works well for numbers between 0 an 256, but I need it to send also larger numbers:
import bpy
import math
from math import degrees
import serial
ser = serial.Serial('COM5',9600,timeout=1)
def my_handler(scene):
data = ''
eulx = bpy.data.objects['Cube'].rotation_euler
x = degrees(eulx.x)
data = bytes([int(x)])
ser.write(data)
bpy.app.handlers.frame_change_post.append(my_handler)
So it send the correct value as long the cube did not turn more than 256 degrees. But I canĀ“t get it run to send the values for rotations larger than that.
Bytes(x) writes out ā\x00ā for every integer value of 1 so youāre probably maxing out what the serial port can send out at once. You probably want to use str.encode on a char represatation;
as I thought, the decode function does not work on the arduino.
The arduino code which was working also for 0 to 256 is:
//This is an example of how you would control 1 stepper
#include <AccelStepper.h>
int motorSpeed = 9600; //maximum steps per second (about 3rps / at 16 microsteps
int motorAccel = 80000; //steps/second/second to accelerate
int motorDirPin = 9; //digital pin 2
int motorStepPin = 8; //digital pin 3
//set up the accelStepper intance
//the "1" tells it we are using a driver
AccelStepper stepper(1, motorStepPin, motorDirPin);
void setup(){
stepper.setMaxSpeed(motorSpeed);
stepper.setSpeed(motorSpeed);
stepper.setAcceleration(motorAccel);
Serial.begin(9600);
stepper.moveTo(0); //move 32000 steps (should be 10 rev)
}
int num;
void loop()
{
num = Serial.read();
stepper.moveTo(num);
stepper.run();
}
Im not familiar with Arduino (but after reading up on it it seems really cool! Maybe ill give it a try when i find some free time). Iāve found a bit of ideas about whatās going on.
Most examples i find have the Serial.read() inside a while loop. According to arduino reference docs, Serial.read() in the arduino code will only read a single byte. A byte is 8 bits so 2**8 = 256 possible values. However there is a serial receive buffer which holds up to 64 bytes, and i think you have to put read() in a loop using Serial.available() until you have read all the bytes. Mabye try something like this :
int num = 0;
void setup() {
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
}
void loop() {
numbuffer = 0;
while(Serial.available() > 0) {
[INDENT=2]// read the incoming byte and cumulate
numbuffer += Serial.read();[/INDENT]
}
num = numbuffer;
}
I would also suggest using (int).to_bytes on the python side. This would allow for signed integers :
>>> x = -333
>>> x
-333
>>> xb = x.to_bytes(2,ābigā,signed=True)
>>> xb
bā\xfe\xb3ā
The proposed Arduino sketch will not receive the integer value correctly. You need to shift the bytes received into the Arduino āintā. In this case, using ābigā in the to_bytes() call is the easiest way to send this since that will send the most significant value first.
int num = 0;
void setup() {
Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
}
void loop() {
int numbuffer = 0;
int bytesToRead = 2; // The call to 'to_bytes' wanted 2 bytes
while(Serial.available() > 0 && bytesToRead > 0) {
// read the incoming byte and cumulate
int serialByte = Serial.read();
// The "<<" moves the bits in the number the supplied
// number of bits to the right. So "(1 << 1) == 2"
// If you did this with regular, base10 numbers you
// would see something like "15 << 2 == 1,500"
// it adds "0" to the end of the number.
numbuffer += serialByte << ((bytesToRead-1) * 8);
bytesToRead = bytesToRead -1;
}
num = numbuffer;
}
It movesā¦very very curious and absolutely wrong, but it moves for the first time!
Basically the stepper should turn the same degrees as an animated cube on the y-axis. Forward and backward. I made these codes:
Blender:
import bpy
import math
from math import degrees
import serial
ser = serial.Serial('COM5',9600,timeout=1)
def my_handler(scene):
data = ''
eulx = bpy.data.objects['Cube'].rotation_euler
x = degrees(eulx.y)
data = str.encode(chr(int(x*100)))
ser.write(data)
bpy.app.handlers.frame_change_post.append(my_handler)
Arduino:
#include <AccelStepper.h>
int motorSpeed = 1000;
int motorAccel = 20000;
int motorDirPin = 9;
int motorStepPin = 8;
int num = 0;
AccelStepper stepper(1, motorStepPin, motorDirPin);
void setup(){
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
digitalWrite(5, LOW);
digitalWrite(6, LOW);
digitalWrite(7, LOW);
stepper.setMaxSpeed(motorSpeed);
stepper.setSpeed(motorSpeed);
stepper.setAcceleration(motorAccel);
stepper.moveTo(0);
Serial.begin(9600);
}
void loop()
{
int numbuffer = 0;
int bytesToRead = 2;
while(Serial.available() > 0 && bytesToRead > 0){
int serialByte = Serial.read();
numbuffer += serialByte << ((bytesToRead-1)*8);
bytesToRead = bytesToRead-1;
}
num = numbuffer;
stepper.runToNewPosition(num);
}
When I run these scripts and jump just one frame forward (the cube turns 1.396 degrees), the stepper also runs forwardā¦hundrets of full rotations(canĀ“t count it, because its too fast ;-)). After a few minutes running forward, it starts running backward. Also very much full rotations. I think about ten minutes later it stopps.
I think itĀ“s a problem how the arduino interprets the serial data, but IĀ“ve no plan how to fix it. And in future i planed to drive 3 steppers Well a very long way to reach this goal.
Maybe anyone has an idea?
By so far, thanks to all of you who tried pushing me forward!!!
Well at least thing are working. Its easier to debug working code.
First thing that i can see is in your python code :
data = str.encode(chr(int(x*100)))
You multiply by 100 to get the decimal precision i talked about?
Except you dont seem to multiply with the reciprocal on the Arduino side. So even if the number were correctly received, it would be 100x too big.
You have to multiply the final value by 0.01 on the arduino side, at which point it would be a float and not an int.
(OR, If you dont want to use a floating point value, make sure to adjust the motor settings since you would now be working in hundreths of degrees rather than degrees.)
Also, you are maybe not receiving the correct number since on the python side you are still converting to char and then using str.encode(). If you read Kastoriaās post again youāll see heās referencing the(int).to_bytes()method for converting the number value.
Ah! I missed the fact that you had fixed-point math going on. Besides that, I agree with Muffy that you are sending the wrong data to your Arduino sketch. The Arduino code I wrote is expecting 16-bit binary numbers to come though the serial port. You are sending ASCII strings so your sketch might be getting any value roughly between [-32,000, 32,000].
Fixed-point is a reasonable way to go but I personally would go with sending a regular, binary float to the Arduino. Iām not sure what kind of resolution your stepper motor has so this might be overkill. For example, Iāve done this with servos but sub-degree resolution didnāt make a difference to me so I only needed integer values. Can you accurately position the stepper at ā1.55ā degrees vs ā1.60ā degrees? If not, then a float is probably overkill. But here is a Python and Arduino sketch you can try. As usual, Iām not at home so this might not compile and my contain logic errors.
Python
import bpy
import math
import struct
from math import degrees
import serial
ser = serial.Serial('COM5',9600,timeout=1)
def my_handler(scene):
data = ''
eulx = bpy.data.objects['Cube'].rotation_euler
x = degrees(eulx.y)
# I'm going to use 'struct' to 'pack' the float into binary data
# And packing the data into binary form 'Big Endian' Check the docs on 'sturct'
s = struct.Struct('> f')
packedData = s.pack(x)
# Write the raw, binary float to the serial port.
ser.write(packedData)
bpy.app.handlers.frame_change_post.append(my_handler)
THen there are some changes required on the Arduino sketch. My original code assumed you were receiving a 16-bit integer value. Now we are getting 4-byte floating point value. So I have to catch those bytes and then turn them into a āfloatā.
#include <AccelStepper.h>
int motorSpeed = 1000;
int motorAccel = 20000;
int motorDirPin = 9;
int motorStepPin = 8;
float* num = NULL; // CHANGE: I've made 'num' a pointer to a float for tricky C-casting
AccelStepper stepper(1, motorStepPin, motorDirPin);
void setup(){
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
digitalWrite(5, LOW);
digitalWrite(6, LOW);
digitalWrite(7, LOW);
stepper.setMaxSpeed(motorSpeed);
stepper.setSpeed(motorSpeed);
stepper.setAcceleration(motorAccel);
stepper.moveTo(0);
Serial.begin(9600);
}
void loop(){
int numbuffer = 0;
int bytesToRead = 4; // CHANGE: receiving 4 bytes
while(Serial.available() > 0 && bytesToRead > 0){
int serialByte = Serial.read();
numbuffer += serialByte << ((bytesToRead-1)*8);
bytesToRead = bytesToRead-1;
}
// Tricky memory stuff. 'num' and 'numbuffer' now point to the same place in memory
// But if you access that place in memory by accessing 'num' then the system treats it
// as a floating point value. If you access it though 'numbuffer' then it is treated an an
// integer value. I need both because the reading code is doing bit-manipulations that
// work best when the memory is treated as an integer. But your stepper motor wants
// the floating point values.
num = (float*)&numbuffer;
// CHANGE: this is now '*num'. I hope that stepper.runToNewPosition takes float values.
stepper.runToNewPosition(*num);
}
It seems more likely that there is a problem with the sample code. I didnāt have time to tested it out on a live system so there might be bugs there. In particular, what is the resolution of your stepper motor? Does your library accept a float value or is it integer based?
my stepper has a resolution of 200. 200 steps per revolution. I really donāt know if this library accepts float values??? I will try to clear this question.
Ah, you are not sending useful data to your stepper driver. You need to read the docs for your components carefully to get the results that you want.
Source data: Blender Euler rotations
These are absolute angles specified in radians.
Target: Your 200-step motor on the Big Easy Driver:
Your motor has a resolution of 200 steps.
The Big Easy Driver defaults to having 1/16 steps
Therefore,
To move your motor 1 full rotation you have to tell it to move 200 * 16 = 3200 units.
What you have to do is figure out when you want to convert from radians into steps. This could be
done in Python or in the Arduino sketch. Doing it in Python will save load on your Arduino and make
the serial code a bit easier (wonāt have to use āstructā). Doing it in Arduino will take more processing
there but will save rewriting Python code. This isnāt enough work to stress the Arduino so Iām going
to do it there because it will take less code. Be aware that if you ask about this on a dedicated
HW forum, people will flame you for āstressing the uCā and ādoing it wrong.ā I say, if it works then
itās right.
So, right now the Arduino sketch is receiving a floating point radian value. The radian value should
be [0, 2*pi) and you have to convert this to the range [0, 3200).
Iāve also normalized the input angle on the Arduino to be in the range [0, 2 * pi). If you have negative
angles or angles larger than 360 degrees, then this might cause the motor to turn in the wrong direction.
#include <AccelStepper.h>
int motorSpeed = 1000;
int motorAccel = 20000;
int motorDirPin = 9;
int motorStepPin = 8;
float* num = NULL; // CHANGE: I've made 'num' a pointer to a float for tricky C-casting
AccelStepper stepper(1, motorStepPin, motorDirPin);
void setup(){
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
digitalWrite(5, LOW);
digitalWrite(6, LOW);
digitalWrite(7, LOW);
stepper.setMaxSpeed(motorSpeed);
stepper.setSpeed(motorSpeed);
stepper.setAcceleration(motorAccel);
stepper.moveTo(0);
Serial.begin(9600);
}
void loop(){
int numbuffer = 0;
int bytesToRead = 4; // CHANGE: receiving 4 bytes
while(Serial.available() > 0 && bytesToRead > 0){
int serialByte = Serial.read();
numbuffer += serialByte << ((bytesToRead-1)*8);
bytesToRead = bytesToRead-1;
}
// Tricky memory stuff. 'num' and 'numbuffer' now point to the same place in memory
// But if you access that place in memory by accessing 'num' then the system treats it
// as a floating point value. If you access it though 'numbuffer' then it is treated an an
// integer value. I need both because the reading code is doing bit-manipulations that
// work best when the memory is treated as an integer. But your stepper motor wants
// the floating point values.
num = (float*)&numbuffer;
// normalize 'num' so that it is in the range [0, 2* pi]
while(*num >= 360.0)
*num -= 360;
while(*num < 0)
*num += 360;
// 'num' is currently storing the angle in radians from Blender.
// Convert from the range [0, 2*pi] to [0, 3200]
long steps = (long)((100.0/3.1415) * (*num));
steps *= 16;
// Move the stepper motor to the new position
stepper.runToNewPosition(steps);
}