PySerial - How to send numbers larger than 256?

Hi everybody,

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.

Maybe anyone have an ideaā€¦

Thank you!

Mat

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;

example :

>>> bytes(300)
bā€™\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ā€™

vsā€¦

>>> str.encode(chr(300))
bā€™\xc4\xacā€™

http://docs.python.org/3.3/library/functions.html#chr
http://docs.python.org/3.3/library/stdtypes.html#str.encode

You can then decode using decode() and ord() as such :

>>> x = str.encode(chr(300))
>>> x
bā€™\xc4\xacā€™

>>> ord(x.decode())
300

You can probably up your precision using this method ;

>>> rot = 345.86
>>> x = str.encode( chr( int(rot*100) ) )
>>> x
bā€™\xe8\x9c\x9aā€™

>>> ord( x.decode() ) * 0.01
345.86

Thank you very much Muffy,

I will try it on monday. But I donā€™t know if the decoding works on an arduino.

wish you a nice weekend!

Mat

Hi again,

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();
}

Any ideas how the make it work?

Thanks again!
Mat

Hi again

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ā€™

1 Like

Good Morning,

sounds like a good idea :-). I will try it as soon as possible and let you know!

Thank you for helping me!

Mat

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;
}

Hi everbody,

thanks for helping me.

@ Kastoria

Can you please also explain me how the blender(python)-code should look like? IĀ“ve tried all those things you and Muffy explained, but nothing works.

Thank you very much!

Mat

Hello again!

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 :slight_smile: 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!!!

Mat

Well at least thing are working. :slight_smile: 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);
}

Hi everybody!

Back from vacations and immediately tried out your new ideas! Thank you!

But this time everything stands still :frowning: I also tried it with the moveTo() argument and also nothing happened.

Is it possible that the accelstepper-library is the problem?

Thank you very much!
Mat

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?

Hello,

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.

Mat

Oh, Iā€™ve forgottenā€¦Iā€™m using the Big Easy Driver!

thank you!
Mat

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);
}


Hi Kastoria,

thanks for answering and not giving me up :slight_smile:

The bad news is that the new code also didnĀ“t work. But with the driver you are absolutely right. The default setting ist 3200 steps per revolution.

I set the pins 5,6 and 7 to ā€œLowā€, means the resolution is 200 steps for making it a little bit easier.

The motor should also turn more than one revolution. For example go 10 revolutions forward, then 3 backwards and another 15 forward and so on.

Thank you very very much!!!
Mat

If it is not working, then something is broken and you will have to debug it.

I would recommend changing your Arduino sketch in the following way:

  • Read in single byte values
  • Connect though the Arduino Com Console and type in known values ASCII values.
    • On windows you can hold ALT and type a number on your number pad and it will enter that ASCII code
    • Hold ALT+2+0+0, release ALT and you will get ā€˜ā•šā€™
    • Send that and see if your motor moves 1 revolution. Debug your stepper call until it does.

Once that is working then restore the code to read a ā€˜floatā€™ from serial port and pass in a 360 and debug until
you get a revolution.

Hi,

the accelstepper library does definitely not support float values. I will try to check the code in pieces. Next step is the bufferā€¦

Thank you!

Mat