when I try to read data from a MCP3424 ADC, I get unexpected, wrong results. I know the device is connected, butthe results I'm reading are wrong
I write to channels 3 and 4 of the ADC. When I read the result back, the data in the config register doesn't match what I programmed
import smbus
import time
# Get I2C bus
bus = smbus.SMBus(1)
# I2C address of the device
MCP3425_DEFAULT_ADDRESS = 0x68
# MCP3425 Configuration Command Set
MCP3425_CMD_NEW_CNVRSN = 0x80 # Initiate a new conversion(One-Shot Conversion mode only)
MCP3425_CMD_MODE_CONT = 0x10 # Continuous Conversion Mode
MCP3425_CMD_MODE_ONESHOT = 0x00 # One-Shot Conversion Mode
MCP3425_CMD_SPS_240 = 0x00 # 240 SPS (12-bit)
MCP3425_CMD_SPS_60 = 0x04 # 60 SPS (14-bit)
MCP3425_CMD_SPS_15 = 0x08 # 15 SPS (16-bit)
MCP3425_CMD_GAIN_1 = 0x00 # PGA Gain = 1V/V
MCP3425_CMD_GAIN_2 = 0x01 # PGA Gain = 2V/V
MCP3425_CMD_GAIN_4 = 0x02 # PGA Gain = 4V/V
MCP3425_CMD_GAIN_8 = 0x03 # PGA Gain = 8V/V
MCP3425_CMD_READ_CNVRSN = 0x00 # Read Conversion Result Data
MCP3425_CMD_CH4 =0x60
MCP3425_CMD_CH3 =0x40
class MCP3425():
def __init__(self):
self.config_command()
def config_command(self):
"""Select the Configuration Command from the given provided values"""
CONFIG_CMD4 = (MCP3425_CMD_CH4| MCP3425_CMD_MODE_CONT | MCP3425_CMD_SPS_60 | MCP3425_CMD_GAIN_2)
bus.write_byte(MCP3425_DEFAULT_ADDRESS, CONFIG_CMD4)
CONFIG_CMD3 = (MCP3425_CMD_CH3| MCP3425_CMD_MODE_CONT | MCP3425_CMD_SPS_240 | MCP3425_CMD_GAIN_1)
bus.write_byte(MCP3425_DEFAULT_ADDRESS, CONFIG_CMD3)
print ('-C-', CONFIG_CMD4, CONFIG_CMD3)
def read_adc(self, channel):
"""Read data back from MCP3425_CMD_READ_CNVRSN(0x00), 2 bytes
raw_adc MSB, raw_adc LSB"""
data = bus.read_i2c_block_data(MCP3425_DEFAULT_ADDRESS, (MCP3425_CMD_READ_CNVRSN | channel), 3)
print (channel, data)
# Convert the data to 12-bits
raw_adc = ((data[0] & 0x0F) * 256) + data[1]
if raw_adc > 2047 :
raw_adc -= 4095
return {'r' : raw_adc}
#from MCP3425 import MCP3425
mcp3425 = MCP3425()
while True :
adc = mcp3425.read_adc(MCP3425_CMD_CH4)
print ("Digital Value of Analog Input 4: %d "%(adc['r']))
adc = mcp3425.read_adc(MCP3425_CMD_CH3)
print ("Digital Value of Analog Input 3: %d "%(adc['r']))
print (" ********************************* ")
time.sleep(0.8)
I write 117 (01110101)to channel 4, and 80 (01010000) to channel 3 config registers. Which means for both channels I should get 3 bytes back: 2 data bytes and one config register byte
this is the printout I'm getting, no values read (Ch4+ is connected to voltage divider (2.5v), Ch4- and Ch3- are connected to ground, Ch3+ is floating) and byte 3 is just the address, not the Config register
-C- 117 80
96 [0, 0, 96]
Digital Value of Analog Input 4: 0
64 [0, 0, 64]
Digital Value of Analog Input 3: 0
*********************************
now it took some time but I found the issue.
The trick was that the MCP3434 is not a 4-channel ADC, but a single ADC with an 4-input multiplexer. Once I realized that, the schematic from the datasheet was obvious. It is just not explained very well in the description on how to handle dataflow on the I2C bus.
So you can not configure two ADC's to measure in parallel but when you need to read more channels you have to
1) configure/start channel 1
2) read data
3) configure/start channel 2
4) read data
the read data step is thus channel independant.
Related
I am trying to get temperature sensor to work on pico pi, using PIN 16. I got DS18X20 and onewire library from GitHub. I'm trying to get temperature reading but it shows an error.
I tried running this code:
import time
import machine
import ds18x20
import onewire
# the device is on GPIO12
dat = machine.Pin(22)
# create the onewire object
on = onewire.OneWire(dat)
ds = ds18x20.DS18X20(on)
# scan for devices on the bus
roms = ds.scan()
print('found devices:', roms)
# loop 10 times and print all temperatures
for i in range(10):
print('temperatures:', end=' ')
ds.convert_temp()
time.sleep_ms(750)
for rom in roms:
print(ds.read_temp(rom), end=' ')
print()
This is DS18X0 library:
"""
DS18x20 temperature sensor driver for MicroPython.
This driver uses the OneWire driver to control DS18S20 and DS18B20
temperature sensors. It supports multiple devices on the same 1-wire bus.
The following example assumes the ground of your DS18x20 is connected to
Y11, vcc is connected to Y9 and the data pin is connected to Y10.
>>> from pyb import Pin
>>> gnd = Pin('Y11', Pin.OUT_PP)
>>> gnd.low()
>>> vcc = Pin('Y9', Pin.OUT_PP)
>>> vcc.high()
>>> from ds18x20 import DS18X20
>>> d = DS18X20(Pin('Y10'))
Call read_temps to read all sensors:
>>> result = d.read_temps()
>>> print(result)
[20.875, 20.8125]
Call read_temp to read the temperature of a specific sensor:
>>> result = d.read_temp(d.roms[0])
>>> print(result)
20.25
If only one DS18x20 is attached to the bus, then you don't need to
pass a ROM to read_temp:
>>> result = d.read_temp()
>>> print(result)
20.25
"""
from onewire import OneWire
class DS18X20(object):
def __init__(self, pin):
self.ow = OneWire(pin)
# Scan the 1-wire devices, but only keep those which have the
# correct # first byte in their rom for a DS18x20 device.
self.roms = [rom for rom in self.ow.scan() if rom[0] == 0x10 or rom[0] == 0x28]
def read_temp(self, rom=None):
"""
Read and return the temperature of one DS18x20 device.
Pass the 8-byte bytes object with the ROM of the specific device you want to read.
If only one DS18x20 device is attached to the bus you may omit the rom parameter.
"""
rom = rom or self.roms[0]
ow = self.ow
ow.reset()
ow.select_rom(rom)
ow.write_byte(0x44) # Convert Temp
while True:
if ow.read_bit():
break
ow.reset()
ow.select_rom(rom)
ow.write_byte(0xbe) # Read scratch
data = ow.read_bytes(9)
return self.convert_temp(rom[0], data)
def read_temps(self):
"""
Read and return the temperatures of all attached DS18x20 devices.
"""
temps = []
for rom in self.roms:
temps.append(self.read_temp(rom))
return temps
def convert_temp(self, rom0, data):
"""
Convert the raw temperature data into degrees celsius and return as a float.
"""
temp_lsb = data[0]
temp_msb = data[1]
if rom0 == 0x10:
if temp_msb != 0:
# convert negative number
temp_read = temp_lsb >> 1 | 0x80 # truncate bit 0 by shifting, fill high bit with 1.
temp_read = -((~temp_read + 1) & 0xff) # now convert from two's complement
else:
temp_read = temp_lsb >> 1 # truncate bit 0 by shifting
count_remain = data[6]
count_per_c = data[7]
temp = temp_read - 0.25 + (count_per_c - count_remain) / count_per_c
return temp
elif rom0 == 0x28:
return (temp_msb << 8 | temp_lsb) / 16
else:
assert False
This is onewire library:
#!/usr/bin/env python
#
# Copyright (c) 2019, Pycom Limited.
#
# This software is licensed under the GNU GPL version 3 or any
# later version, with permitted additional terms. For more information
# see the Pycom Licence v1.0 document supplied with this file, or
# available at https://www.pycom.io/opensource/licensing
#
"""
OneWire library for MicroPython
"""
import time
import machine
class OneWire:
CMD_SEARCHROM = const(0xf0)
CMD_READROM = const(0x33)
CMD_MATCHROM = const(0x55)
CMD_SKIPROM = const(0xcc)
def __init__(self, pin):
self.pin = pin
self.pin.init(pin.OPEN_DRAIN, pin.PULL_UP)
print("init")
def init(self, pin):
self.pin = pin
self.pin.init(pin.OPEN_DRAIN, pin.PULL_UP)
print("init")
def reset(self):
"""
Perform the onewire reset function.
Returns True if a device asserted a presence pulse, False otherwise.
"""
sleep_us = time.sleep_us
disable_irq = machine.disable_irq
enable_irq = machine.enable_irq
pin = self.pin
pin(0)
sleep_us(480)
i = disable_irq()
pin(1)
sleep_us(60)
status = not pin()
enable_irq(i)
sleep_us(420)
return status
def read_bit(self):
sleep_us = time.sleep_us
enable_irq = machine.enable_irq
pin = self.pin
pin(1) # half of the devices don't match CRC without this line
i = machine.disable_irq()
pin(0)
sleep_us(1)
pin(1)
sleep_us(1)
value = pin()
enable_irq(i)
sleep_us(40)
return value
def read_byte(self):
value = 0
for i in range(8):
value |= self.read_bit() << i
return value
def read_bytes(self, count):
buf = bytearray(count)
for i in range(count):
buf[i] = self.read_byte()
return buf
def write_bit(self, value):
sleep_us = time.sleep_us
pin = self.pin
i = machine.disable_irq()
pin(0)
sleep_us(1)
pin(value)
sleep_us(60)
pin(1)
sleep_us(1)
machine.enable_irq(i)
def write_byte(self, value):
for i in range(8):
self.write_bit(value & 1)
value >>= 1
def write_bytes(self, buf):
for b in buf:
self.write_byte(b)
def select_rom(self, rom):
"""
Select a specific device to talk to. Pass in rom as a bytearray (8 bytes).
"""
self.reset()
self.write_byte(CMD_MATCHROM)
self.write_bytes(rom)
def crc8(self, data):
"""
Compute CRC
"""
crc = 0
for i in range(len(data)):
byte = data[i]
for b in range(8):
fb_bit = (crc ^ byte) & 0x01
if fb_bit == 0x01:
crc = crc ^ 0x18
crc = (crc >> 1) & 0x7f
if fb_bit == 0x01:
crc = crc | 0x80
byte = byte >> 1
return crc
def scan(self):
"""
Return a list of ROMs for all attached devices.
Each ROM is returned as a bytes object of 8 bytes.
"""
devices = []
diff = 65
rom = False
for i in range(0xff):
rom, diff = self._search_rom(rom, diff)
if rom:
devices += [rom]
if diff == 0:
break
return devices
def _search_rom(self, l_rom, diff):
if not self.reset():
return None, 0
self.write_byte(CMD_SEARCHROM)
if not l_rom:
l_rom = bytearray(8)
rom = bytearray(8)
next_diff = 0
i = 64
for byte in range(8):
r_b = 0
for bit in range(8):
b = self.read_bit()
if self.read_bit():
if b: # there are no devices or there is an error on the bus
return None, 0
else:
if not b: # collision, two devices with different bit meaning
if diff > i or ((l_rom[byte] & (1 << bit)) and diff != i):
b = 1
next_diff = i
self.write_bit(b)
if b:
r_b |= 1 << bit
i -= 1
rom[byte] = r_b
return rom, next_diff
class DS18X20(object):
def __init__(self, onewire):
self.ow = onewire
self.roms = [rom for rom in self.ow.scan() if rom[0] == 0x10 or rom[0] == 0x28]
self.fp = True
try:
1/1
except TypeError:
self.fp = False # floatingpoint not supported
def isbusy(self):
"""
Checks wether one of the DS18x20 devices on the bus is busy
performing a temperature convertion
"""
return not self.ow.read_bit()
def start_conversion(self, rom=None):
"""
Start the temp conversion on one DS18x20 device.
Pass the 8-byte bytes object with the ROM of the specific device you want to read.
If only one DS18x20 device is attached to the bus you may omit the rom parameter.
"""
if (rom==None) and (len(self.roms)>0):
rom=self.roms[0]
if rom!=None:
rom = rom or self.roms[0]
ow = self.ow
ow.reset()
ow.select_rom(rom)
ow.write_byte(0x44) # Convert Temp
def read_temp_async(self, rom=None):
"""
Read the temperature of one DS18x20 device if the convertion is complete,
otherwise return None.
"""
if self.isbusy():
return None
if (rom==None) and (len(self.roms)>0):
rom=self.roms[0]
if rom==None:
return None
else:
ow = self.ow
ow.reset()
ow.select_rom(rom)
ow.write_byte(0xbe) # Read scratch
data = ow.read_bytes(9)
return self.convert_temp(rom[0], data)
def convert_temp(self, rom0, data):
"""
Convert the raw temperature data into degrees celsius and return as a fixed point with 2 decimal places.
"""
temp_lsb = data[0]
temp_msb = data[1]
if rom0 == 0x10:
if temp_msb != 0:
# convert negative number
temp_read = temp_lsb >> 1 | 0x80 # truncate bit 0 by shifting, fill high bit with 1.
temp_read = -((~temp_read + 1) & 0xff) # now convert from two's complement
else:
temp_read = temp_lsb >> 1 # truncate bit 0 by shifting
count_remain = data[6]
count_per_c = data[7]
if self.fp:
return temp_read - 25 + (count_per_c - count_remain) / count_per_c
else:
return 100 * temp_read - 25 + (count_per_c - count_remain) // count_per_c
elif rom0 == 0x28:
temp = None
if self.fp:
temp = (temp_msb << 8 | temp_lsb) / 16
else:
temp = (temp_msb << 8 | temp_lsb) * 100 // 16
if (temp_msb & 0xf8) == 0xf8: # for negative temperature
temp -= 0x1000
return temp
else:
assert False
This is the error I'm getting:
>>> %Run -c $EDITOR_CONTENT
init
Traceback (most recent call last):
File "<stdin>", line 11, in <module>
File "ds18x20.py", line 33, in __init__
File "onewire.py", line 30, in __init__
AttributeError: 'OneWire' object has no attribute 'OPEN_DRAIN'
>>>
I've tried multiple ways to resolve this and watched a bunch of videos on youtube, but not sure what causes this issue.
I have tried my luck with these codes to read the data from the DDSU666-H and it seems that I am able to read data, but I don't know what to do with it. In theory it is a list of data, but I am not able to understand what I am receiving or how to translate it into something I can use
import serial # import the module
def banner_bottom():
print(' +-------------------------------------------+')
print(' | SPACE |')
print(' +-------------------------------------------+')
COM_Port = serial.Serial('COM3')
COM_Port.baudrate = 9600 # set Baud rate
COM_Port.bytesize = 8 # Number of data bits = 8
COM_Port.parity = 'N' # No parity
COM_Port.stopbits = 1 # Number of Stop bits = 1
COM_Port.setRTS(1) #RTS=1,~RTS=0 so ~RE=0,Receive mode enabled for MAX485
COM_Port.setDTR(1) #DTR=1,~DTR=0 so DE=0,(In FT232 RTS and DTR pins are inverted)
#~RE and DE LED's on USB2SERIAL board will be off
RxedData = COM_Port.readline()
print(' ',RxedData, '\n')
COM_Port.close() # Close the Serial port
banner_bottom()# Display the bottom banner
Output:
b'\x7f~\xbb\xff\xfb\xe3=\x7f~_cuI_\x0e\x7f~\xbb\xff\xfb\xe3=\x7f~_]\xd3V\'\t\x7f~\xbb\xff\xfb\xe3=\x7f~__\x07\xf5tU\x7f~\xbb\xff\xfb\xe3=\x7f~_[y\x8d\xba\x00\x7f~\xbb\xff\xfb\xe3=\x7f~_YsGe\x18\x7f~\xbb\xff\xfb\xe3=\x7f~_Y\xe3\x06n\x16\x7f~\xbb\xff\xfb\xe3=\x7f~__O]I\x1d\x7f~\xbb\xff\xfb\xe3=\x7f~_ao\xff\xf0\x05\x7f~\xbb\xff\xfb\xe3=\x7f~_co-O\x10\x7f~\xbb\xff\xfb\xe3=\x7f~__\x15\x117\x03\x7f~\xbb\xff\xfb\xe3=\x7f~__!-\xc9/\x7f~\xbb\xff\xfb\xe3=\x7f~__}\xed|\x00\x7f~\xbb\xff\xfb\xe3=\x7f~_c\xb7+\xda\x00\x7f~\xbb\xff\xfb\xe3=\x7f~_e\x8f"U\x01\x7f~\xfd\xff\xfb]\x1a\x7f~_W\xaf\x08\x975\x00\x7f~\xfd\xff\xfb]\x1a\x7f~_W\xaf\x08\x975\x00\x7f~\xfd\xff\xfb]\x1a\x7f~_W\xaf\x08\x975\x00\x7f~\xbb\xff\xfb\xe3=\x7f~_]\xd3V\'\t\x7f~\xbb\xff\xfb\xe3=\x7f~__\xd9\xadb\x08\x7f~\xbb\xff\xfb\xe3=\x7f~__\xc5[P\x05\x7f~\xbb\xff\xfb\xe3=\x7f~__\x91\xfef\x00\x7f~\xbb\xff\xfb\xe3=\x7f~_c3\xb3T\x0f\x7f~\xbb\xff\xfb\xe3=\x7f~_e\xbd\x01\xb0\x03\x7f~\xbb\xff\xfb\xe3=\x7f~_e?y\x84\x19\x7f~\xbb\xff\xfb\xe3=\x7f~_\xe349\xac\n'
Code 2:
#!/usr/bin/env python3
import serial
port = 'COM3'
ComPort = serial.Serial(port)
ComPort.baudrate = 9600
ComPort.bitesize = 8
ComPort.parity = 'N'
ComPort.stopbits = 1
dataIn=ComPort.read(6)
print(dataIn)
ComPort.close()
Ouput:
b'\x7f~\xbb\xff\xfb\xe3'
I've tried to translate the output into something I can understand, but I haven't been able to see anything either.
binary_data = b'\x7f~\xbb\xff\xfb\xe3'
aa = binary_data.hex()
print(aa)
#OUT: 7f7ebbfffbe3
bb = ''.join(['%02x' % b for b in binary_data])
print(bb)
#OUT: 7f7ebbfffbe3
s = binary_data.decode('cp855')
print(s)
#OUT:~╗ чÑ
What can I try to resolve this?
Here in California, I have purchased some Nova SDS011 PM sensors. When attempting to read from these sensors using Ivan Kalchev's git repo, I get mixed results. I can send commands to the sensor. e.g. sensor.sleep(sleep=<True/False>) will turn the fan on and off. However attempting to query the sensor to return PM2.5 and PM10 data returns a byte string that does not match the check sum. A couple examples are in the code snip-it below. As you can see, bytes 2 and 6 appear to be corrupt, and furthermore, the response is two bytes shorter than what is expected from the documentation.
Any Idea whats going on here? Im hoping this is simply a problem with pyserial. I have produced the same results with two sensors.
>>> sensor.sleep(sleep=False)
>>> cmd
'\xaa\xb4\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x02\xab'
>>> sensor.ser.write(cmd)
19
>>> sensor.ser.readline()
'\xaa\xc0]\x01\xba\x01\xc2*\x05\xab'
>>> sensor.ser.write(cmd)
19
>>> sensor.ser.readline()
'\xaa\xc0c\x01\xbc\x01\xc2*\r\xab'
>>> sensor.ser.write(cmd)
19
>>> sensor.ser.readline()
'\xaa\xc0d\x01\xbf\x01\xc2*\x11\xab'
So this occurred because the python was written for python 3, and my raspberry pi had 2.7. below is the code for 2.7. Thanks to Ivan for putting the original together.
"""This module provides an abstraction for the SDS011 air partuclate densiry sensor.
"""
import struct
import serial
#TODO: Commands against the sensor should read the reply and return success status.
class SDS011(object):
"""Provides method to read from a SDS011 air particlate density sensor
using UART.
"""
HEAD = b'\xaa'
TAIL = b'\xab'
CMD_ID = b'\xb4'
# The sent command is a read or a write
READ = b"\x00"
WRITE = b"\x01"
REPORT_MODE_CMD = b"\x02"
ACTIVE = b"\x00"
PASSIVE = b"\x01"
QUERY_CMD = b"\x04"
# The sleep command ID
SLEEP_CMD = b"\x06"
# Sleep and work byte
SLEEP = b"\x00"
WORK = b"\x01"
# The work period command ID
WORK_PERIOD_CMD = b'\x08'
def __init__(self, serial_port, baudrate=9600, timeout=2,
use_query_mode=True):
"""Initialise and open serial port.
"""
self.ser = serial.Serial(port=serial_port,
baudrate=baudrate,
timeout=timeout)
self.ser.flush()
self.set_report_mode(active=not use_query_mode)
def _execute(self, cmd_bytes):
"""Writes a byte sequence to the serial.
"""
self.ser.write(cmd_bytes)
def _get_reply(self):
"""Read reply from device."""
raw = self.ser.read(size=10)
data = raw[2:8]
if len(data) == 0:
return None
if (sum(ord(d) for d in data) & 255) != ord(raw[8]):
return None #TODO: also check cmd id
return raw
def cmd_begin(self):
"""Get command header and command ID bytes.
#rtype: list
"""
return self.HEAD + self.CMD_ID
def set_report_mode(self, read=False, active=False):
"""Get sleep command. Does not contain checksum and tail.
#rtype: list
"""
cmd = self.cmd_begin()
cmd += (self.REPORT_MODE_CMD
+ (self.READ if read else self.WRITE)
+ (self.ACTIVE if active else self.PASSIVE)
+ b"\x00" * 10)
cmd = self._finish_cmd(cmd)
self._execute(cmd)
self._get_reply()
def query(self):
"""Query the device and read the data.
#return: Air particulate density in micrograms per cubic meter.
#rtype: tuple(float, float) -> (PM2.5, PM10)
"""
cmd = self.cmd_begin()
cmd += (self.QUERY_CMD
+ b"\x00" * 12)
cmd = self._finish_cmd(cmd)
self._execute(cmd)
raw = self._get_reply()
if raw is None:
return None #TODO:
data = struct.unpack('<HH', raw[2:6])
pm25 = data[0] / 10.0
pm10 = data[1] / 10.0
return (pm25, pm10)
def sleep(self, read=False, sleep=True):
"""Sleep/Wake up the sensor.
#param sleep: Whether the device should sleep or work.
#type sleep: bool
"""
cmd = self.cmd_begin()
cmd += (self.SLEEP_CMD
+ (self.READ if read else self.WRITE)
+ (self.SLEEP if sleep else self.WORK)
+ b"\x00" * 10)
cmd = self._finish_cmd(cmd)
self._execute(cmd)
self._get_reply()
def set_work_period(self, read=False, work_time=0):
"""Get work period command. Does not contain checksum and tail.
#rtype: list
"""
assert work_time >= 0 and work_time <= 30
cmd = self.cmd_begin()
cmd += (self.WORK_PERIOD_CMD
+ (self.READ if read else self.WRITE)
+ bytes([work_time])
+ b"\x00" * 10)
cmd = self._finish_cmd(cmd)
self._execute(cmd)
self._get_reply()
def _finish_cmd(self, cmd, id1=b"\xff", id2=b"\xff"):
"""Add device ID, checksum and tail bytes.
#rtype: list
"""
cmd += id1 + id2
checksum = sum(d for d in bytearray(cmd[2:])) % 256
cmd += chr(checksum) + self.TAIL
return cmd
def _process_frame(self, data):
"""Process a SDS011 data frame.
Byte positions:
0 - Header
1 - Command No.
2,3 - PM2.5 low/high byte
4,5 - PM10 low/high
6,7 - ID bytes
8 - Checksum - sum of bytes 2-7
9 - Tail
"""
raw = struct.unpack('<HHxxBBB', data[2:])
checksum = sum(v for v in bytearray(data[2:8])) % 256
if checksum != data[8]:
return None
pm25 = raw[0] / 10.0
pm10 = raw[1] / 10.0
return (pm25, pm10)
def read(self):
"""Read sensor data.
#return: PM2.5 and PM10 concetration in micrograms per cude meter.
#rtype: tuple(float, float) - first is PM2.5.
"""
byte = 0
while byte != self.HEAD:
byte = self.ser.read(size=1)
d = self.ser.read(size=10)
if d[0:1] == b"\xc0":
data = self._process_frame(byte + d)
return data
I want to send packet over Modbus TCP. I want to use:
But I can not send this way how can I send this packet? (I don't know something will be)
req = struct.pack(
'Something', transaction, identifier, length, unitid, func_code, reg_addr
)
These are my variables:
transaction=0x01
identifier=0x00
length=[0x00,0x06]
unitid=0x01
func_code=0x03
reg_addr=[0x13,0x14,0x15]
At first you can use pymodbus library with very features.
Also struct.pack() not support a list as argument.
0001 0000 0006 11 03 006B 0003 is a standard example of Modbus-TCP packet which contained:
0001: Transaction Identifier
0000: Protocol Identifier
0006: Message Length (6 bytes to follow)
11: The Unit Identifier (17 = 11 hex)
03: The Function Code (read Analog Output Holding Registers)
006B: The Data Address of the first register requested. (40108-40001 = 107 =6B hex)
0003: The total number of registers requested. (read 3 registers 40108 to 40110)
Reference
Thus, you can create a Modbus-TCP packet with the above example:
import struct
transaction = 0x0001
identifier = 0x0000
length = 0x0006
unitid = 0x11
fcode = 0x03 # Holding register fcode.
reg_addr = 0x006B # Register address.
count = 0x0003 # Read three register.
total_pack_string = '0x{:04x}{:04x}{:04x}{:02x}{:02x}{:04x}{:04x}'.format(
transaction, identifier, length, unitid, fcode, reg_addr, count
)
total_pack_hex = hex(int(total_pack_string, 16))
'''Or with using pack method.'''
pack_ = struct.pack(
'>HHHBBHH', transaction, identifier, length, unitid, fcode, reg_addr, count
)
# Then send the pack_ or total_pack_hex using a TCP-Socket.
[NOTE]:
transaction is 2Byte == Short == H
identifier is 2Byte == Short == H
length is 2Byte == Short == H
unitid is 1Byte == B
fcode is 1Byte == B
reg_addr is 2Byte == Short == H
count is 2Byte == Short == H
B is unsigned byte
H is unsigned short
Thus, the format will be like this >HHHBBHH
Using pymodbus equivalent:
from pymodbus.client.sync import ModbusTcpClient
unitid = 0x11
fcode = 0x03 # Holding register fcode.
reg_addr = 0x006B # Register address.
count = 0x0003 # Read three register.
cli = ModbusTcpClient('127.0.0.1', port=502)
if cli.connect():
res = cli.read_holding_registers(reg_addr, count=count, unit=unitid)
if not res.isError():
print(res.registers)
else:
print('There is an error.')
cli.close()
else:
print('Error in connection.')
I'm new to Python and currently working on a project on my Pi 3 mod b. I use an Adafruit ADC1015 to convert analogue signal. However, even if i have the code to get some volt measurments, i get an error of " AttributeError: 'int' object has no attribute 'readADCSingleEnded'".
To explain that, the python script i'm trying to run is the following:
#!/usr/bin/python
import time, signal, sys
from Adafruit_ADS1x15 import ADS1x15
def signal_handler(signal, frame):
print 'You pressed Ctrl+C!'
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
ADS1015 = 0x00
ADS1115 = 0x01
gain = 4096 # +/- 4.096V
sps = 250 # 250 samples per second
# Initialise the ADC using the default mode (use default I2C address)
# Set this to ADS1015 or ADS1115 depending on the ADC you are using!
adc = ADS1015(ic=ADS1015)
# Read channel 0 in single-ended mode using the settings above
volts=adc.readADCSingleEnded(0, gain, sps) / 1000
# To read channel 3 in single-ended mode, +/- 1.024V, 860 sps use:
# volts = adc.readADCSingleEnded(3, 1024, 860)
print "%.6f" % (volts)
The "ADS1x15" file we import contains the following code related to the error:
# Constructor
def __init__(self, address=0x48, ic=__IC_ADS1015, debug=False):
# Depending on if you have an old or a new Raspberry Pi, you
# may need to change the I2C bus. Older Pis use SMBus 0,
# whereas new Pis use SMBus 1. If you see an error like:
# 'Error accessing 0x48: Check your I2C address '
# change the SMBus number in the initializer below!
self.i2c = Adafruit_I2C(address)
self.address = address
self.debug = debug
# Make sure the IC specified is valid
if ((ic < self.__IC_ADS1015) | (ic > self.__IC_ADS1115)):
if (self.debug):
print "ADS1x15: Invalid IC specfied: %h" % ic
return -1
else:
self.ic = ic
# Set pga value, so that getLastConversionResult() can use it,
# any function that accepts a pga value must update this.
self.pga = 6144
def readADCSingleEnded(self, channel=0, pga=6144, sps=250):
"Gets a single-ended ADC reading from the specified channel in mV. \
The sample rate for this mode (single-shot) can be used to lower the noise \
(low sps) or to lower the power consumption (high sps) by duty cycling, \
see datasheet page 14 for more info. \
The pga must be given in mV, see page 13 for the supported values."
# With invalid channel return -1
if (channel > 3):
if (self.debug):
print "ADS1x15: Invalid channel specified: %d" % channel
return -1
# Disable comparator, Non-latching, Alert/Rdy active low
# traditional comparator, single-shot mode
config = self.__ADS1015_REG_CONFIG_CQUE_NONE | \
self.__ADS1015_REG_CONFIG_CLAT_NONLAT | \
self.__ADS1015_REG_CONFIG_CPOL_ACTVLOW | \
self.__ADS1015_REG_CONFIG_CMODE_TRAD | \
self.__ADS1015_REG_CONFIG_MODE_SINGLE
# Set sample per seconds, defaults to 250sps
# If sps is in the dictionary (defined in init) it returns the value of the constant
# othewise it returns the value for 250sps. This saves a lot of if/elif/else code!
if (self.ic == self.__IC_ADS1015):
config |= self.spsADS1015.setdefault(sps, self.__ADS1015_REG_CONFIG_DR_1600SPS)
else:
if ( (sps not in self.spsADS1115) & self.debug):
print "ADS1x15: Invalid pga specified: %d, using 6144mV" % sps
config |= self.spsADS1115.setdefault(sps, self.__ADS1115_REG_CONFIG_DR_250SPS)
# Set PGA/voltage range, defaults to +-6.144V
if ( (pga not in self.pgaADS1x15) & self.debug):
print "ADS1x15: Invalid pga specified: %d, using 6144mV" % sps
config |= self.pgaADS1x15.setdefault(pga, self.__ADS1015_REG_CONFIG_PGA_6_144V)
self.pga = pga
# Set the channel to be converted
if channel == 3:
config |= self.__ADS1015_REG_CONFIG_MUX_SINGLE_3
elif channel == 2:
config |= self.__ADS1015_REG_CONFIG_MUX_SINGLE_2
elif channel == 1:
config |= self.__ADS1015_REG_CONFIG_MUX_SINGLE_1
else:
config |= self.__ADS1015_REG_CONFIG_MUX_SINGLE_0
# Set 'start single-conversion' bit
config |= self.__ADS1015_REG_CONFIG_OS_SINGLE
# Write config register to the ADC
bytes = [(config >> 8) & 0xFF, config & 0xFF]
self.i2c.writeList(self.__ADS1015_REG_POINTER_CONFIG, bytes)
# Wait for the ADC conversion to complete
# The minimum delay depends on the sps: delay >= 1/sps
# We add 0.1ms to be sure
delay = 1.0/sps+0.0001
time.sleep(delay)
# Read the conversion results
result = self.i2c.readList(self.__ADS1015_REG_POINTER_CONVERT, 2)
if (self.ic == self.__IC_ADS1015):
# Shift right 4 bits for the 12-bit ADS1015 and convert to mV
return ( ((result[0] << 8) | (result[1] & 0xFF)) >> 4 )*pga/2048.0
else:
# Return a mV value for the ADS1115
# (Take signed values into account as well)
val = (result[0] << 8) | (result[1])
if val > 0x7FFF:
return (val - 0xFFFF)*pga/32768.0
else:
return ( (result[0] << 8) | (result[1]) )*pga/32768.0
I believed this would run smmothly, as it is a part something that is related to the ADC, but i haven't managed to solve this problem, even if i tried a lot.
Found it. Line
adc = ADS1015(ic=ADS1015)
Should be
adc = ADS1x15(ic=ADS1015)