I am trying to read serial data sent from my PC (Simulink) and control a servo motor position with the data. Simulink transmits the data as binary, the Real-Life-Value is a short float. I have included some frankenstien code, I've been trying everything. Maybe that's the problem. I have searched my toes off through the forums... debugging is hard since the COM port is used and I can't see the built-in serial monitor
Sorry if this is terse, I lost my old post by using the back button. It was witty and everything XD. I am happy to fill in details I forgot. Arduino Uno, Windows xp, some wires...
if (Serial.available() > 0) { // only loop the serial structure if its serialing
digitalWrite(8,LOW); // red off // light green if serial data, red if NO serial
digitalWrite(9,HIGH); // green on
byte inbyte = '/0';
int timeout = 1000; // waits 1 second for a number
long startTime = millis(); // now is the start time
delay(1); // try a delay
while (millis() - startTime < timeout && inbyte != '*') { // this is the header character in the Simulink bloc. the loop waits for it
inbyte = Serial.read(); // change the inbyte to the waiting byte
}
if (inbyte == '*') { // this means its the start of a number
startTime = millis(); // start time is now
while (millis() - startTime < timeout && Serial.available() < 6) { // allow 6 at least bytes to fill the buffer
}
int i = 0; // start the counter
while (Serial.peek() != '\r') { // search till you see the terminator
angl[i] = Serial.read(); // read each character into a character array
++i; // increment i?
if (angl[i] < '0' || angl[i] > 9) {
tsv = false; // if one character is bad, its all bad
}
}
if (tsv == true) {
union u_tag {
byte b[4];
float fvalu;
} u; // this is a union http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1207242838
u.b[0] = angl[0];
u.b[1] = angl[1];
u.b[2] = angl[2];
u.b[3] = angl[3]; // still part of the union
angval = u.fvalu; // could it be a real value?
float ang = angval*90; // convert the scaled angvalue (0 --> 1) to degrees
int angLED = angval*255; // prepare for the PWM signal
// int angLEDpwm = abs(angLED); // absolute value for PWM LED // truncated?
// check if the value looks write
analogWrite(6,angLED); // debug
blue.write(ang); // write the servo
}
}
}
else {
digitalWrite(8,HIGH); // red ON
digitalWrite(9,LOW); // green OFF
}
}
This is my first project with the arduino, its amazing. I feel like everyone should know about these.
Simulink transmits the data as binary, the Real-Life-Value is a short float.
Floats are all the same size. No such thing as a short float.
while (Serial.peek() != '\r') { // search till you see the terminator
angl[i] = Serial.read(); // read each character into a character array
++i; // increment i?
if (angl[i] < '0' || angl[i] > 9) {
tsv = false; // if one character is bad, its all bad
}
You are then reading character data...
A float is 4 bytes on the Arduino, not 6. The bytes are not all in the range '0' to '9'.
Good to know. I'm still learning about all the different types. I guess I was referring to the different types in Matlab (single precision vs double precision). If I can assemble each number into a 4-byte array, is there a built-in method to convert it back into a number (float)?
Does Serial.read handle binary data, or is it looking for characters only?
will I have to manually assemble these 4 bytes into a number? (I am referring to the way that Wikipedia tells me that floats are represented with 4 bytes.
The way a float is stored in memory is completely different from the way the value looks when it is printed out. When printed out, the characters '0' through '9' are used. As is the decimal point.
If matlab is writing binary data (4 bytes) to the serial port, you need to read 4 bytes, and reassemble them as a float.
Each byte will hold a value between 0 and 255. The values '0' and '9' define a very small subset of the total range of valid values.
Does Serial.read handle binary data
Yes.
will I have to manually assemble these 4 bytes into a number? (I am referring to the way that Wikipedia tells me that floats are represented with 4 bytes.
Yes. It's probably easier to do it programatically, though.
I am trying to read serial data sent from my PC (Simulink) and control a servo motor position with the data.
The Servo::write() and Servo::writeMicroseconds() function take integers as input. So, why are we talking about floats?
I am making this too hard for myself. I converted the float before its sent from the PC, so now I am sending an integer -90 < int < +90 that will be written to the servo with *servo.*write(int).
I can't seem to find how to convert the two bytes into the integer programatically. Do I need to create an array? what is the syntax for a conversion?
My brain is saturated on this problem. I have been at it for days :~I will definitely post my solution so that someone with this same problem can solve it faster in the future
if (Serial.available() > 0) { // only loop the serial structure if its serialing
digitalWrite(8,LOW); // red off // light green if serial data, red if NO serial
digitalWrite(9,HIGH); // green on
byte inbyte = '\0'; // null?
int timeout = 1000; // waits 1 second for a number
long startTime = millis(); // now is the start time
delay(1); // try a delay
while (millis() - startTime < timeout && inbyte != '*') { // this is the header character in the Simulink bloc. the loop waits for it
inbyte = Serial.read(); // change the inbyte to the waiting byte
}
if (inbyte == '*') { // this means its the start of a number
tsv = true; //
startTime = millis(); // start time is now
while (millis() - startTime < timeout && Serial.available() < 6) { // allow 6 at least bytes to fill the buffer
}
int angval = Serial.read(); // read an integer
if (tsv == true) {
int angLED = (angval/90)*255; // prepare for the PWM signal
// int angLEDpwm = abs(angLED); // absolute value for PWM LED // truncated?
// check if the value looks write
analogWrite(6,angLED); // debug
blue.write(angval); // write servo?
delay(30);
}
}
}
else {
digitalWrite(8,HIGH); // red ON
digitalWrite(9,LOW); // green OFF
}
Some simple servo control code that might be of interest to verify the servo hardware is workng.
// zoomkat 10-4-10 serial servo test
// type servo position 0 to 180 in serial monitor
// for writeMicroseconds, use a value like 1500
// for IDE 0019 and later
// Powering a servo from the arduino usually DOES NOT WORK.
String readString;
#include <Servo.h>
Servo myservo; // create servo object to control a servo
void setup() {
Serial.begin(9600);
myservo.attach(7); //the pin for the servo control
Serial.println("servo-test-21"); // so I can keep track of what is loaded
}
void loop() {
while (Serial.available()) {
delay(1);
if (Serial.available() >0) {
char c = Serial.read(); //gets one byte from serial buffer
readString += c; //makes the string readString
}
}
if (readString.length() >0) {
Serial.println(readString); //so you can see the captured string
int n;
char carray[6]; //converting string to number
readString.toCharArray(carray, sizeof(carray));
n = atoi(carray);
myservo.writeMicroseconds(n); // for microseconds
//myservo.write(n); //for degees 0-180
readString="";
}
}
Thaks for the reply! I know my hardware is working from other codes, my problem is reading teh binary serial data (2 byte integer) and converting this data into something that I can use with servo.write. This is what I'm currently trying...
if (inbyte == '*') { // this means its the start of a number
tsv = true; //
startTime = millis(); // start time is now
while (millis() - startTime < timeout && Serial.available() < 4) { // allow 4 at least bytes to fill the buffer
}
for (int i=0; i<2; i++) {
b[i] = Serial.read();
}
angval = int(b); // read an integer
if (tsv == true) {
int angLED = (angval/90)*255; // prepare for the PWM signal
// int angLEDpwm = abs(angLED); // absolute value for PWM LED // truncated?
// check if the value looks write
analogWrite(6,angLED); // debug
blue.write(angval); // write servo?
angval = 0; // reset?
Serial.print(angLED);
//delay(30);
my problem is reading teh binary serial data (2 byte integer) and converting this data into something that I can use with servo.write.
Without knowing what the two bytes are or what they repersent, it is hard to think of what needs to be done to them to make them relate to a servo control signal. Is one of the bytes more significant than the other?
I would think one would do something like get the decimal value of the least significant byte (0-255), and get the decimal value of the most significant byte, multiply that by 255 (or 256?), then add the two together to get the total decimal equivelant.
Zoom, that solved it. for someone like me (very new), its a snakeoil solution, but it worked! You're the man.
Now I have a serial timing issue to deal with, but that will have to wait for tomorrow. Below is the code that worked, although the serial timing is off. The servo should be following a smooth sine wave 0 --> 180º but it very jumpy.
for (int i=0; i<2; i++) {
b[i] = Serial.read();
}
int angval2 = b[0]+b[1]*256;
//angval = int(b); // read an integer
int new_ang = angval2;
for someone like me (very new), its a snakeoil solution, but it worked!
It's hardly a snakeoil solution. It simply reverses the process that the sender used to extract the least significant and most significant bytes from the integer prior to sending.
Now I have a serial timing issue to deal with
Perhaps if you'd show all of your code, instead of just snippets, we could help with that, too. What baud rate are you using? Are there any delay()s anywhere?
Hey guys, I have finally solved the issue of controlling a servo with serial communication. The PC (Simulink) is sending a 16 bit integer in binary format that is the desired servo position in degrees. Thanks to PaulS and Zoomkat for all the help .
The serial timing issue was solved by eliminating the Serial.flush command (commented out in the code)
Thanks again Forum,
Brian
// Control the small blue servo
// Brian Leach
// april 12 2011
// integer value from simulink. use the signed 16 bit integer from simulink with servo.write
// Simulink is sending a value (in degrees) to position the servo.
#include <Servo.h>
Servo blue;
int ang; // create integer to hold servo angle
int new_ang = 90;
int old_ang = 90;
byte b[2]; // byte array
void setup() {
blue.attach(5, 1025, 1975); // servoName.attach(pin, min, max)
Serial.begin(38400); // begin serial @ 38400 bits/sec
pinMode(8,OUTPUT); // debug lights. red no serial
pinMode(9,OUTPUT); // debug light green yea serial
pinMode(6,OUTPUT); // output 6 LED
//pinMode(5,OUTPUT); // output 5 servo
byte homey = 90;
blue.write(homey); // set servo to mid-point
delay(1000);;
}
void loop() {
//Serial.flush(); // clear the serial buffer before reading new data *** this was the problem. clearing data unnecessarily
memset(b, '\0', 2);
boolean tsv = false; // boolean to decide if the servo should be sent commands
int angval = 0; // integer value of angle in degrees
if (Serial.available() > 0) { // only loop the serial structure if its serialing
digitalWrite(8,LOW); // red off // light green if serial data, red if NO serial
digitalWrite(9,HIGH); // green on
char inbyte = '\0'; // null?
int timeout = 1000; // sets timeout to wait 1 second for a number
long startTime = millis(); // now is the start time
//delay(1); // try a delay
while (millis() - startTime < timeout && inbyte != '*') { // this is the header character in the Simulink bloc. the loop waits for it
inbyte = Serial.read(); // change the inbyte to the waiting byte
}
if (inbyte == '*') { // this means its the start of a number
tsv = true; // this might eventually be an error check
startTime = millis(); // start time is now
while (millis() - startTime < timeout && Serial.available() < 3) { // allow 2 at least bytes to fill the buffer
}
for (int i=0; i<2; i++) {
b[i] = Serial.read();
}
int angval2 = b[0]+b[1]*256; // converts the two bytes to an integer. thanks to Zoomkat for this solution
int new_ang = angval2; // sets the angle value for this loop
if (old_ang != new_ang) { // if new angle equals old angle don't write anything
if (tsv == true) {
int angLED = (angval2/180)*255; // prepare for the PWM signal
// int angLEDpwm = abs(angLED); // absolute value for PWM LED // truncated?
// check if the value looks write
analogWrite(6,angLED); // debug
blue.write(angval2); // write servo?
old_ang = angval2; // reset?
Serial.print(angLED);
}
}
//delay(.5);
}
}
else {
digitalWrite(8,HIGH); // red ON
digitalWrite(9,LOW); // green OFF
analogWrite(6,0); // servo lite OFF
}
delayMicroseconds(2);
}
Hey, I tried your code to run my motors and all it does is go straight to 100% and saturates. What I'm trying to do is run my usb throttle to send numerical values to my edf. My setup is throttle at (0-100%)*Motor bandwidth + deadzone in milliseconds. In other words it should start from deadzone and throttle from 0 to 100%. I sent it through a signed int16 block, then a saturation block (with inherited datatype) then to serial send. I basically coped and pasted your code and modified it for writeMicroseconds.
Any thoughts on this?
Running Arduino Mega w/ ATMega1280
edit: The code I used is here
//The script simply reads the serial data received as throttle
//in milliseconds
#include <Servo.h>
Servo motor; //create servo object
Servo motor1; //create servo object
int new_throttle = 0;
int old_throttle = 0;
byte b[2]; // byte array
//-----------------------------
//----------Activate-----------
void activate(Servo motor,int n)
{
motor.attach(n); // attach motor to pin n
motor.writeMicroseconds(1000); // write zero throttle to activate
delay(1000);
}
void setup()
{
// activate motor first
activate(motor,3); // forward motor 3
activate(motor,4); // forward motor 1
//connect to the computer
Serial.begin(38400); // initialize serial ports 0 & 3
}
void loop()
{
if (Serial.available() > 0) { // only loop the serial structure if its serialing
int tval12 = 1280; // create integer to hold throttle value
boolean tsv = false;
char inbyte = '\0'; // null?
int timeout = 20; // sets timeout to wait 20 milliseconds for a number
long startTime = millis(); // now is the start time
while (millis() - startTime < timeout && inbyte != '*') { // this is the header character in the Simulink block. the loop waits for it
inbyte = Serial.read(); // change the inbyte to the waiting byte
}
//reading data now
if (inbyte == '*') { // this means its the start of a number
tsv = true; // this might eventually be an error check
startTime = millis(); // start time is now
while (millis() - startTime < timeout && Serial.available() < 3) { // allow 2 at least bytes to fill the buffer
}
for (int i=0; i<2; i++) {
b[i] = Serial.read();
}
int tval2 = b[0]+b[1]*255;
int new_throttle = tval2; // sets the throttle value for this loop
if (old_throttle != new_throttle) { //if throttle has changed write new one
if (tsv == true) {
//saturtate thrust at max (2000ms)
if (new_throttle>= 2000)
{
new_throttle = 2000;
}
//Serial.print(new_throttle);
motor.writeMicroseconds(new_throttle); // write motor
motor1.writeMicroseconds(new_throttle); // write motor
old_throttle = new_throttle; //save last value
}
}
}
}
}