1
votes

I have a problem while trying to send some values from Python 3.x to Arduino using Serial Communication.
It working fine when the value is smaller than 255, but when it's greater than 255, error will happen.

I'm using Struct.pack in Python and Serial.read() in Arduino


Python code:

import cv2
import numpy as np
from serial import Serial
import struct

arduinoData = Serial('com6', 9600)

cap = cv2.VideoCapture(0)
hand_cascade = cv2.CascadeClassifier('hand.xml')

while(True):
    ret, frame = cap.read()
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    handdetect = hand_cascade.detectMultiScale(gray, 1.6, 3)

    for (x, y, w, h) in handdetect:   
        cv2.rectangle(frame, (x, y), (x + w, y + h), (127, 127, 0), 2)

        xcenter = int(x + w/2)
        ycenter = int(y + h/2)

        #This is where i send values to serial port
        arduinoData.write(struct.pack('>II',xcenter,ycenter))

    cv2.imshow('Webcam', frame)
    k = cv2.waitKey(25) & 0xff
    if k == 27:
        break

cap.release()
cv2.destroyAllWindows()

Arduino code:

int SerialData[8];
const int led = 7;
int xcenter;
int ycenter;

void setup(){
  Serial.begin(9600);
  pinMode(led, OUTPUT);
}

void loop(){
  if (Serial.available() >= 8){
    for (int i = 0; i < 8; i++){
      SerialData[i] = Serial.read();
    }
    xcenter = (SerialData[0]*1000) + (SerialData[1]*100) + (SerialData[2]*10) + SerialData[3];
    ycenter = (SerialData[4]*1000) + (SerialData[5]*100) + (SerialData[6]*10) + SerialData[7];

    if (xcenter <= 200){
      digitalWrite(led, LOW);
    }
    else if(xcenter > 200){
      digitalWrite(led, HIGH);
    }
   //Serial.flush();
  }
}

Like I said at the beginning of this topic, when xcenter > 200 && xcenter <= 255, the LED is turned ON (it's mean the code working fine).
But when xcenter > 255, the LED is OFF (something wrong here).
I think I already read all 8 bytes in Arduino code and used unsigned int >II in struct.pack, so what and where is my false?
I'm appreciate for all help! Thank you!


EDIT and FIXED.
"It doesn't pack int into digits (0-9), it packs them into bytes (0-255)"_

So here is the false:

xcenter = (SerialData[0]*1000) + (SerialData[1]*100) + (SerialData[2]*10) + SerialData[3];  
ycenter = (SerialData[4]*1000) + (SerialData[5]*100) + (SerialData[6]*10) + SerialData[7];  

Changed to this (for the large values):

long result = long((unsigned long(unsigned char(SerialData[0])) << 24) | (unsigned long(unsigned char(SerialData[1])) << 16)
            | (unsigned long(unsigned char(SerialData[2])) << 8) | unsigned char(SerialData[3])); 

Or changed to this (for the small values):

xcenter = (SerialData[2]*256) + SerialData[3];
ycenter = (SerialData[6]*256) + SerialData[7]; 

Or this (for the small values too):

int result = int((unsigned int(unsigned char(SerialData[2])) << 8) | unsigned char(SerialData[3]));

And the code gonna work perfectly!

1
It doesn't pack int into digits (0-9), it packs them into bytes (0-255). 256 is packed into 00 00 01 00 which you unpack as 10x1, while 255 is 00 00 00 ff which is 1x255 in your case. Change powers of 10 to powers of 256 (2^8). Alternatively don't pack them, just convert them to string, smth like "{:04d}{:04d}".format(intA, intB)IcedLance
Thank you @IcedLance for showing me my false. I'm new to code, can i ask you what is the first byte stand for?.Le Phong
you can check out this link to get an idea of how this works, but in short: decimal (base 10) go from 0 to 9 so 356 = 3*10*10 + 5*10 + 6 while bytes go from 0 to 255, which means 03 05 06 = 3*256*256 + 5*256 + 6. I write them in 2 letters because it is common to represent byte values with pairs of hexadecimal digits (base 16, 256=16*16). In your case first byte basically stands for `xcenter/(256*256*256). It'll be 0 until xcenter becomes bigger than 256^3.IcedLance
Dear @IcedLand, thank you for your help, i already fixed all of my problem! You save my day, thanks again and wish you have a nice day! Best regard.Le Phong
@IcedLance please post the solution as an answer and accept it!B.Letz

1 Answers

0
votes

This code will not work correctly for large values...

int xcenter = (SerialData[0]*256*256*256) + (SerialData[1]*256*256) + (SerialData[2]*256) + SerialData[3];

The problem is that the input is 4 bytes wide, which is a long integer, while the integer size on the arduino is only 2 bytes wide, which means that 256 * 256 = 0x10000 & 0xFFFF = 0 !

To make sure you do not run into problems for values wider than 2 bytes, one must use shift operations.

This gives:

long result = long((unsigned long(unsigned char(SerialData[0])) << 24) | (unsigned long(unsigned char(SerialData[1])) << 16)
            | (unsigned long(unsigned char(SerialData[2])) << 8) | unsigned char(SerialData[3]));

Alternatively, if you do not expect large values, only use two bytes from the input. Make sure to do the calculus using unsigned values, or you may run into problems !!!

int result = int((unsigned int(unsigned char(SerialData[2])) << 8) | unsigned char(SerialData[3]));

This is very verbose, but it's safe for all input types. For example, the solution presented by the OP would not work if SerialData[] was a char array, which it should be, to avoid wasting memory.