Difference I2C Sensor Reading Raspberry Pi and Arduino - python

I am using the Sensirion SFM3300 flow sensor and can read the correct values with the Arduino with the following code (I2C):
#include <Wire.h>
void setup() {
// put your setup code here, to run once:
Wire.begin();
Serial.begin(115200);
Wire.beginTransmission(byte(0x40));
Wire.write(byte(0x10));
Wire.write(byte(0x00));
Wire.endTransmission();
}
void loop() {
// put your main code here, to run repeatedly:
delay(100);
Wire.requestFrom(0x40,2);
uint16_t a = Wire.read();
uint8_t b = Wire.read();
a = (a<<8) | b;
float flow = ((float)a - 32768) / 120;
Serial.println(flow);
}
But using the Raspberry Pi I have written the nearly the same code, hoping that it also will works.
This is the code:
from smbus2 import SMBus
import time
import numpy as np
address=0x40
bus = SMBus(1)
def write(value):
bus.write_byte(address,value)
write(0x10)
write(0x00)
while True:
time.sleep(0.1)
a = np.uint16(bus.read_byte(0x40))
b = np.uint8(bus.read_byte(0x40))
a = (a<<8) | b
flow = (float(a)-32768)/120
print(flow)
The code really looks the same, but I only get -273,06666666666 as a return value. Does somebody knows where are the differences between Raspberry Pi and Arduino I2C and can help me to get the right values on the Pi?

You can use read_i2c_block_data(addr, offset, numOfBytes) method to get more than 1 byte of data from i2c. the return data is a list of bytes. So it is very easy to convert into an integer.
Edited based on datasheet and Arduino sketch
Here is the complete code for Python that should matched the Arduino example:
from SMBus2 import SMBus
import time
offset = 32768
scale = 120
addr = 0x40
cmd = [0x10, 0x00]
with SMBus(1) as bus:
bus.write_i2c_block_data(addr, 0, cmd)
time.sleep(0.1)
block = bus.read_i2c_block_data(addr, 0, 3)
reading = block[0] * 256 + block[1]
crc = block[2] # should do some crc check for error
flow = (reading - offset)/scale
print(flow)

I don't think your read process in python is correct. Reading from port 40 two times is different from reading two bytes from port 40.
I suggest to use read_byte_data(0x40, 0, 2) and process that with struct.unpack(">H").

I found a working solution. It would be nice if a I2C-expert could tell me why the following code is working instead of the python code above.
from fcntl import ioctl
from struct import unpack
from smbus import SMBus
address = 0x40
SMBus(1).write_byte_data(address,16,0)
i2c = open("/dev/i2c-1", "rb", buffering=0)
ioctl(i2c,0x0703,address)
i2c.read(3)
d0,d1,c = unpack('BBB', i2c.read(3))
d = d0 << 8 | d1
a = (float(d)-32768.)/120
print(a)

Related

Sending multiple sensor data from arduino to python,any idea?

So I have this problem,I'm trying to send sensor data from arduino to python through serial communication.
But the output is a kind of wierd.
Does anyone have an idea on this?
Arduino code: to send sensor data
void setup(){
Serial.begin(9600);
}
void loop(){
int sensor1 = 20;
int sensor2 = 40;
int sensor3 = 60;
Serial.write(sensor1);
}
python code: to receive sent data from arduino
import serial,time
ser = serial.Serial("/dev/ttyACM1",9600,timeout=1)
while True:
data = ser.read()
time.sleep(1)
print("data:",data)
output :
data: b'\x14'
target :
data: 20
second target : sending multiple sensor data in a single serial.write().
data: 20 40 60
By using Serial.write() on the arduino side, you are sending the sensor integers as single byte value characters, (as mentioned here in the reference) but on the receiving side seem to be expecting whole lines of input (readline()). The value printed, b'\x14' (hexadecimal 14) is decimal 20, so the transmission was actually correct. You can solve your problem by sending actual text integers from the arduino with:
Serial.println(sensor1);

Sending 32bit integer from python script to arduino via serial for use in driving RGB strip

first post so go easy on me.
I have made a python script which takes a screenshot of the current display and finds the most frequently reappearing RGB colour value.
I am attempting to send this to via serial to an arduino pro micro (with some supplementary components) to drive a 12v LED strip.
I am bit-shifting the individual red, green and blue values, and am adding them together into an unsigned 32 bit integer to be send to the arduino which will then use a bitmask to then get the original values back using PWM pins on the arduino control three MOSFETS (N-Channel) to drive the individual colours on the RGB strip. Each colour has 256 values so can be represented in 8bits seen below.
32bit unsigned int
un-used blue green red
00000000 00000000 00000000 00000000
red mask
00000000 00000000 00000000 11111111
green mask
00000000 00000000 11111111 00000000
blue mask
00000000 11111111 00000000 00000000
My issue is that the serial communication between the python script and the arduino doesn't appear to be working, and I cannot figure out why. Is it something to do with the format of the unsigned 32 bit integer I'm sending? I'm not really finding any information on the acceptable formats and have little experience using serial communications.
What I do know is that the arduino circuit is definitely connected correctly as I can fully drive the RGB strip by specifying values on the arduino side.
The python script is definitely connecting to the arduino as I am connecting using hardware information found in windows device manager when the arduino is connected, and while the script is running I cannot use anything else to connect to the COM port used by the arduino.
The python script is calculating and formatting the values as I intend as I can simply print them to console instead of writing to serial to confirm.
from PIL import Image
import serial
import serial.tools.list_ports
import pyscreenshot as ImageGrab
import time
from collections import Counter
if __name__ == '__main__': ###main program###
# open comms with arduino
VID = "2341" #arduino pro micro HEX vendor ID given throught the arduino IDE
PID = "8037" #arduino pro micro HEX product ID given throught the arduino IDE
arduino_pro_micro_port = None
ports = list(serial.tools.list_ports.comports()) #get ports information
for port in ports:
if PID and VID in port.hwid: #look for matching PID and VID
arduino_pro_micro_port = port.device #serial port of arduino nano pro
if arduino_pro_micro_port == None:
print("Couldn't find an ardiuno pro micro to communicate with")
else:
COMPort = serial.Serial(arduino_pro_micro_port, 115200, writeTimeout = 0) #open connection for RGB values to be written
while True: #loop forever
image = ImageGrab.grab() #grab screenshot
image = image.resize((512, 288)) #20% size for faster processing
image = image.load() #load image so pixel information can be interrogated
RGBlist = []
#seperate pixel tuple into lists for red, green and blue
for horizontal in range(0, 512, 1): #for all horizontal pixels
for vertical in range(0, 288, 1): #for all vertical pixels
red = image[horizontal, vertical][0]
blue = image[horizontal, vertical][1] << 8
green = image[horizontal, vertical][2] << 16
RGBlist.append(red + green + blue)
sendLong = Counter(RGBlist).most_common(1)
print("send " + str(sendLong[0][0]))
COMPort.write(sendLong[0][0]) #write RGB to serial port
print("reci "+ str(COMPort.readline())) #read and print line from arduino
time.sleep(0.1) #wait 0.1 seconds
```end of python code
```arduino code
//set pin integers for RGB
int LEDRPin = 5;
int LEDGPin = 6;
int LEDBPin = 9;
//setup variable store for RGB values
unsigned long incomingLong = 0; //store the incomoing byte for processing#
unsigned long redMask = 255; //bitmask for red value
unsigned long greenMask = 65280; //bitmask for green value
unsigned long blueMask = 16711680; //bitmask for blue value
unsigned long Rv = 0; //red value will be stored here
unsigned long Gv = 0; //green value will be stored here
unsigned long Bv = 0; //blue value will be stored here
unsigned long SAVE_Rv = 0; //red value will be saved here
unsigned long SAVE_Gv = 0; //green value will be saved here
unsigned long SAVE_Bv = 0; //blue value will be saved here
void setup() {
//initialise RBG pins as outputs
pinMode(LEDRPin, OUTPUT);
pinMode(LEDGPin, OUTPUT);
pinMode(LEDBPin, OUTPUT);
//start serial comms
Serial.begin(115200);
if(Serial.available())// only send data back if data has been sent
{
incomingLong = Serial.read(); //read RGB values from serial port
Serial.write("Ready");
}
}
void loop() {
delay(300);
if(Serial.available() >= 0) // only send data back if data has been sent
{
incomingLong = Serial.read(); //read RGB values from serial port
Serial.write(incomingLong);
Rv = redMask & incomingLong; //get red value
Gv = greenMask & incomingLong; //get green value
Bv = blueMask & incomingLong; //get blue value
Rv = Rv >> 0; //shift to LSB
Gv = Gv >> 8; //shift to LSB
Bv = Bv >> 16; //shift to LSB
Rv = (int) Rv; //Cast to int
Gv = (int) Gv; //Cast to int
Bv = (int) Bv; //Cast to int
analogWrite(LEDRPin, Rv); //write red value to output pin
analogWrite(LEDGPin, Gv); //write green value to output pin
analogWrite(LEDBPin, Bv); //write blue value to output pin
SAVE_Rv = Rv;
SAVE_Gv = Gv;
SAVE_Bv = Bv;
}
}
```end of arduino code
I've written and tested my watered down communication solution for your case on python27 and arduino Uno but should give you more or less the same results. Test it and let me know if you need clarification or get stuck at some point.
# python27
import serial
import serial.tools.list_ports
import struct
import sys
arduino_pro_micro_port = 'COM3'
COMPort = serial.Serial(arduino_pro_micro_port, 115200, writeTimeout = 0)
while True:
r = 12
g = 145
b = 87
if COMPort.in_waiting:
#COMPort.write(struct.pack("l", 123)) # sending one long directly
COMPort.write(struct.pack("BBB", r, g, b)) # B means unsigned byte
print COMPort.readline()
// Arduino
void setup() {
// put your setup code here, to run once:
//start serial comms
Serial.begin(115200);
Serial.println("Ready");
}
void loop() {
// I'm taking the delay off as that might cause synchronization issues on the receiver side
while (Serial.available() <= 3); // only send data back if data has been sent
int r = Serial.read(); //read RGB values from serial port
int g = Serial.read(); //read RGB values from serial port
int b = Serial.read(); //read RGB values from serial port
Serial.println("RGB: ");
Serial.println(r);
Serial.println(g);
Serial.println(b);
delay(3000);
}

How to send float values from Python to Arduino LCD?

My classmate and I have been working on this project based on this Instructables article https://www.instructables.com/id/Building-a-Simple-Pendulum-and-Measuring-Motion-Wi/, our idea is to make a pendulum, calculate the g force (from the pendulum's period) and then show its value on a LCD we got connected to the Arduino. We got the code up and running (it calculates the period), and we understood that the Arduino has to do some type of conversion (utf-8) to pass the values it gets from the potentiometer to Python. However when we try to send the value we get from calculating the period of the graph back to the arduino and show it on the LCD, it shows 634 or other similiar values, we tried to instead of the decode it does initially, go the other way around with encode, but it won't work. We can't check the value it is getting from the serial, because the serial monitor simply doesn't open while the python script is running. What is the most pratical way we can use to "transfer" floats calculated in a Python script to the Arduino, so that we can calculate g and show it on the screen. Many forums advice to instead of transferring the floats, convert them to strings since it would be easy for the arduino to receive, but we aren't sure that would even work. I'm sure this is a simple question, but we just can't seem to get it. If you find anything else wrong with the code please let me know, we know it's a bit sketchy. Thanks.
Python code:
arduino = serial.Serial('COM3', 115200, timeout=.1) #Open connection to Arduino
samples = 200 #We will take this many readings
angle_data = np.zeros(samples) #Creates a vector for our angle data
time_data = np.zeros(samples) #Creates a vector of same length for time
i = 0;
calibrate = 123 #Value to zero potentiometer reading when pendulum is motionless, read from Arduino
while i!=samples:
data = arduino.readline()[0:-2].decode('utf-8')
if data:
angle_data[i] = (float(data) - calibrate)*math.pi/180
time_data[i] = time.perf_counter()
print(angle_data[i])
i = i + 1
min = np.min(angle_data)
print (min)
min_pos, = np.where(angle_data == min)
min_x = time_data[min_pos]
print (min_x)
nos_left = int(min_pos)
max = 0;
for i in range(nos_left,200):
if angle_data[i] > max: max = angle_data[i]
print (max)
max_pos, = np.where(angle_data == max)
max_x = time_data[max_pos]
print (max_x)
period = (max_x - min_x) * 2
print (period)
gforce = (0.165 * 4 * (math.pi) * (math.pi)) / ((period) * (period))
print (gforce)
value_g = arduino.write(gforce)
plt.plot(time_data,angle_data,'ro')
plt.axis([0,time_data[samples-1],-math.pi,math.pi])
plt.xlabel("Time (seconds)")
plt.ylabel("Angle (Radians)")
plt.title("Pendulum Motion - Measured with Arduino and potentiometer")
plt.show()
arduino.close()
Arduino code
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
int period = 0;
void setup() {
lcd.begin(16, 2);
Serial.begin(115200); // use the same baud-rate as the python side
pinMode(A0,INPUT);
lcd.print(" Pendulo ");
int gforce = 0;
}
void loop() {
// set the cursor to column 0, line 1
// (note: line 1 is the second row, since counting begins with 0):
// print the number of seconds since reset:
int degrees;
degrees = getDegree();
Serial.println(degrees);
Serial.println("\n");
delay(50);
if (Serial.available() > 0) {
// read the incoming byte:
gforce = Serial.read();
Serial.print("I received: ");
Serial.println(gforce, DEC);
}
lcd.setCursor(0, 1);
lcd.print(gforce);
}
int getDegree()
{
int sensor_value = analogRead(A0);
float voltage;
voltage = (float)sensor_value*5/1023;
float degrees = (voltage*300)/5;
return degrees;
}
This appears to be a good case for the Arduino Serial's parseFloat() method:
if (Serial.available() > 0) {
/* instead of Serial.read(), use: */
gforce = Serial.parseFloat();
Serial.print("I received: ");
Serial.println(gforce, DEC);
}
Essentially, it pulls out anything that looks like a float within the received serial data, even if it's mixed with other non-numerical characters.
It also works with Software Serial.

Interpret serialdata in python

I'm currently struggling with an issue. I have an arduino sending serialdata to my raspberry pi. The raspberry pi reads the data and stores it in a database. What i'm struggling with is to get data in the correct order. If i start the script at the correct time, the values get read properly. If i don't they get mixed up.
I have a headerByte sent from the arduino, this value is 999 and it is the first value to be sent each time. Is there a way in python to make 999 the marker for the beginning of every read? My variables will never exceed 999 so this will not be a problem.
Python code:
import serial
import time
values = []
serialArduino = serial.Serial('/dev/ttyACM0', baudrate=9600, timeout=1)
voltageRead = serialArduino.readline()
currentRead = serialArduino.readline()
while True:
voltageRead = serialArduino.readline()
currentRead = serialArduino.readline()
print"V=", voltageRead, "A=", currentRead
Arduino Code:
void loop() {
float voltageRead = analogRead(A0);
float ampsRead = analogRead(A1);
float calculatedVoltage = voltageRead / 103;
float calculatedCurrent = ampsRead / 1;
int headerByte = 999;
Serial.println(headerByte);
Serial.println(calculatedVoltage);
Serial.println(calculatedCurrent);
delay(1000);
}
Your method isn't particularly efficient; you could send everything as a struct from the Arduino (header + data) and parse it on the RPi side with the struct module though your way does have the advantage of simplicity. But, if 999 is the highest value you expect in your readings, then it makes more sense to use a number greater than 999 like 1000. And 999 isn't really a byte.
That said, if "1000" is your header, you can simply check for the header's presence like this:
HEADER = "1000"
serialArduino = serial.Serial('/dev/ttyACM0', baudrate=9600, timeout=1)
while True:
if serialArduino.readline().rstrip() == HEADER: # check for header
voltageRead = serialArduino.readline() # read two lines
currentRead = serialArduino.readline()
print"V=", voltageRead, "A=", currentRead

Read latest character sent from Arduino in Python

I'm a beginner in both Arduino and Python, and I have an idea but I can't get it to work. Basically, when in Arduino a button is pressed, it sends "4" through the serial port. What I want in Python is as soon as it reads a 4, it should do something. This is what I got so far:
import serial
ser = serial.Serial('/dev/tty.usbserial-A900frF6', 9600)
var = 1
while var == 1:
if ser.inWaiting() > 0:
ser.readline(1)
print "hello"
But obviously this prints hello no matter what. What I would need is something like this:
import serial
ser = serial.Serial('/dev/tty.usbserial-A900frF6', 9600)
var = 1
while var == 1:
if ser.inWaiting() > 0:
ser.readline(1)
if last.read == "4":
print "hello"
But how can I define last.read?
I don't know a good way of synchronising the comms with readLine since it's not a blocking call. You can use ser.read(numBytes) which is a blocking call. You will need to know how many bytes Arduino is sending though to decode the byte stream correctly. Here is a simple example that reads 8 bytes and unpacks them into 2 unsigned shorts and a long (the <HHL part) in Python
try:
data = [struct.unpack('<HHL', handle.read(8)) for i in range(PACKETS_PER_TRANSMIT)]
except OSError:
self.emit(SIGNAL("connectionLost()"))
self.connected = False
Here's a reference to the struct.unpack()
The Arduino code that goes with that. It reads two analog sensor values and the micro timestamp and sends them over the serial.
unsigned int SensA, SensB;
byte out_buffer[64];
unsigned int buffer_head = 0;
unsigned int buffer_size = 64;
SensA = analogRead(SENSOR_A);
SensB = analogRead(SENSOR_B);
micr = micros();
out_buffer[buffer_head++] = (SensA & 0xFF);
out_buffer[buffer_head++] = (SensA >> 8) & 0xFF;
out_buffer[buffer_head++] = (SensB & 0xFF);
out_buffer[buffer_head++] = (SensB >> 8) & 0xFF;
out_buffer[buffer_head++] = (micr & 0xFF);
out_buffer[buffer_head++] = (micr >> 8) & 0xFF;
out_buffer[buffer_head++] = (micr >> 16) & 0xFF;
out_buffer[buffer_head++] = (micr >> 24) & 0xFF;
Serial.write(out_buffer, buffer_size);
The Arduino playground and Processing Forums are good places to look around for this sort of code as well.
UPDATE
I think I might have misled you with readLine not blocking. Either way, the above code should work. I also found this other thread on SO regarding the same subject.
UPDATE You don't need to use the analog sensors, that's just what the project I did happened to be using, you are of course free to pass what ever values over the serial. So what the Arduino code is doing is it has a buffer of type byte where the output is being stored before being sent. The sensor values and micros are then written to the buffer and the buffer sent over the serial. The (SensA & 0xFF) is a bit mask operator that takes the bit pattern of the SensA value and masks it with the bit pattern of 0xFF or 255 in decimal. Essetianlly this takes the first 8 bits from the 16 bit value of SensA which is an Arduino short. the next line does the same thing but shifts the bits right by 8 positions, thus taking the last 8 bits.
You'll need to understand bit patterns, bit masking and bit shifting for this. Then the buffer is written to the serial.
The Python code in turn does reads the bits from the serial port 8 bits at a time. Have a look at the struct.unpack docs. The for comprehension is just there to allow sending more than one set of values. Because the Arduino board and the Python code are running out of sync I added that to be able to send more than one "lines" per transmit. You can just replace that with struct.unpack('<HHL',handle.read(8)). Remember that the ´handle.read()´ takes a number of bytes where as the Arduino send code is dealing with bits.
I think it might work with this modifications:
import serial
ser = serial.Serial('/dev/tty.usbserial-A900frF6', 9600)
var = 1
while var == 1:
if (ser.inWaiting() > 0):
ser.readline(1)
print "hello"

Categories