Merge branch 'master' of github.com:jamesbowman/i2cdriver

This commit is contained in:
James Bowman 2019-03-09 17:00:21 -08:00
commit 651dec5ac2
17 changed files with 614 additions and 23 deletions

View File

@ -5,8 +5,9 @@
I2CDriver is a tool for controlling any I2C device from your PC's USB port, I2CDriver is a tool for controlling any I2C device from your PC's USB port,
and can also monitor and capture I2C traffic. and can also monitor and capture I2C traffic.
It connects as a standard USB serial device, so there are no drivers to install. It connects as a standard USB serial device, so there are no drivers to install.
The serial protocol is [very simple](/protocol.md), On the main site
and there are included drivers for [i2cdriver.com](https://i2cdriver.com),
there are drivers for
* Windows/Mac/Linux GUI * Windows/Mac/Linux GUI
* Windows/Mac/Linux command-line * Windows/Mac/Linux command-line

View File

@ -4,13 +4,11 @@ import time
import struct import struct
from collections import OrderedDict from collections import OrderedDict
__version__ = '0.0.2' __version__ = '0.0.3'
PYTHON2 = (sys.version_info < (3, 0)) PYTHON2 = (sys.version_info < (3, 0))
from lm75b import *
import EDS import EDS
import bargraph
class I2CTimeout(Exception): class I2CTimeout(Exception):
pass pass

View File

@ -1,24 +1,17 @@
"""
Example for Bi-Color (Red/Green) 24-Bar Bargraph, based on HT16K33.
Available from Adafruit.
"""
import sys import sys
import serial
import time import time
import struct
import random import random
from i2cdriver import I2CDriver
from ht16k33 import HT16K33 from ht16k33 import HT16K33
class bargraph(HT16K33): class bargraph(HT16K33):
def image(self, bb):
def swiz(b):
bs = [str((b >> n) & 1) for n in range(8)]
return int(bs[7] + bs[0] + bs[1] + bs[2] + bs[3] + bs[4] + bs[5] + bs[6], 2)
bb = [swiz(b) for b in bb]
self.load([b for s in zip(bb,bb) for b in s])
def char(self, c):
n = ord(c)
ch = font[n * 8:n * 8 + 8]
self.image(ch)
def set(self, pix): def set(self, pix):
rr = pix rr = pix
def paint(r, i): def paint(r, i):
@ -33,3 +26,12 @@ class bargraph(HT16K33):
[paint(red, i) for i in range(24) if (pix[i] & 1)] [paint(red, i) for i in range(24) if (pix[i] & 1)]
[paint(grn, i) for i in range(24) if (pix[i] & 2)] [paint(grn, i) for i in range(24) if (pix[i] & 2)]
self.load([red[0], grn[0], red[1], grn[1], red[2], grn[2]]) self.load([red[0], grn[0], red[1], grn[1], red[2], grn[2]])
if __name__ == '__main__':
i2 = I2CDriver(sys.argv[1])
d0 = bargraph(i2)
for i in range(60):
d0.set([random.choice((0,1,2,3)) for i in range(24)])
time.sleep(.08)

210
python/samples/bno080.py Normal file
View File

@ -0,0 +1,210 @@
"""
Example for BNO080 integrated IMU.
Available from Sparkfun.
"""
import sys
import serial
import time
import struct
import random
import math
from i2cdriver import I2CDriver
def hexdump(s):
def toprint(c):
if 32 <= c < 127:
return chr(c)
else:
return "."
def hexline(s):
return (" ".join(["%02x" % c for c in s]).ljust(49) +
"|" +
"".join([toprint(c) for c in s]).ljust(16) +
"|")
return "\n".join([hexline(s[i:i+16]) for i in range(0, len(s), 16)])
CHANNEL_COMMAND = 0
CHANNEL_EXECUTABLE = 1
CHANNEL_CONTROL = 2
CHANNEL_REPORTS = 3
CHANNEL_WAKE_REPORTS = 4
CHANNEL_GYRO = 5
# All the ways we can configure or talk to the BNO080, figure 34, page 36 reference manual
# These are used for low level communication with the sensor, on channel 2
SHTP_REPORT_COMMAND_RESPONSE = 0xF1
SHTP_REPORT_COMMAND_REQUEST = 0xF2
SHTP_REPORT_FRS_READ_RESPONSE = 0xF3
SHTP_REPORT_FRS_READ_REQUEST = 0xF4
SHTP_REPORT_PRODUCT_ID_RESPONSE = 0xF8
SHTP_REPORT_PRODUCT_ID_REQUEST = 0xF9
SHTP_REPORT_BASE_TIMESTAMP = 0xFB
SHTP_REPORT_SET_FEATURE_COMMAND = 0xFD
# All the different sensors and features we can get reports from
# These are used when enabling a given sensor
SENSOR_REPORTID_ACCELEROMETER = 0x01
SENSOR_REPORTID_GYROSCOPE = 0x02
SENSOR_REPORTID_MAGNETIC_FIELD = 0x03
SENSOR_REPORTID_LINEAR_ACCELERATION = 0x04
SENSOR_REPORTID_ROTATION_VECTOR = 0x05
SENSOR_REPORTID_GRAVITY = 0x06
SENSOR_REPORTID_GAME_ROTATION_VECTOR = 0x08
SENSOR_REPORTID_GEOMAGNETIC_ROTATION_VECTOR = 0x09
SENSOR_REPORTID_TAP_DETECTOR = 0x10
SENSOR_REPORTID_STEP_COUNTER = 0x11
SENSOR_REPORTID_STABILITY_CLASSIFIER = 0x13
SENSOR_REPORTID_PERSONAL_ACTIVITY_CLASSIFIER = 0x1E
# Record IDs from figure 29, page 29 reference manual
# These are used to read the metadata for each sensor type
FRS_RECORDID_ACCELEROMETER = 0xE302
FRS_RECORDID_GYROSCOPE_CALIBRATED = 0xE306
FRS_RECORDID_MAGNETIC_FIELD_CALIBRATED = 0xE309
FRS_RECORDID_ROTATION_VECTOR = 0xE30B
# Command IDs from section 6.4, page 42
# These are used to calibrate, initialize, set orientation, tare etc the sensor
COMMAND_ERRORS = 1
COMMAND_COUNTER = 2
COMMAND_TARE = 3
COMMAND_INITIALIZE = 4
COMMAND_DCD = 6
COMMAND_ME_CALIBRATE = 7
COMMAND_DCD_PERIOD_SAVE = 9
COMMAND_OSCILLATOR = 10
COMMAND_CLEAR_DCD = 11
CALIBRATE_ACCEL = 0
CALIBRATE_GYRO = 1
CALIBRATE_MAG = 2
CALIBRATE_PLANAR_ACCEL = 3
CALIBRATE_ACCEL_GYRO_MAG = 4
CALIBRATE_STOP = 5
def normalize(v, tolerance=0.00001):
mag2 = sum(n * n for n in v)
if abs(mag2 - 1.0) > tolerance:
mag = math.sqrt(mag2)
v = tuple(n / mag for n in v)
return v
class BNO080:
def __init__(self, i2, a = 0x4b):
self.i2 = i2
self.a = a
self.seqno = [0] * 8
if 1:
self.sendPacket(CHANNEL_EXECUTABLE, [1])
time.sleep(0.150)
while True:
if not self.receivePacket():
break
self.setFeature(SENSOR_REPORTID_ROTATION_VECTOR, 50000)
def read_quaternion(self):
while True:
r = self.receivePacket()
while r:
tag = r[0]
r = r[1:]
if tag == 0xfb:
# print("Time %d" % struct.unpack("I", r[:4]))
r = r[4:]
elif tag == SHTP_REPORT_COMMAND_RESPONSE:
r = r[16:]
elif tag == 0xfc:
# print("Get Feature Response")
r = r[17:]
elif tag == SENSOR_REPORTID_ACCELEROMETER:
r = r[10:]
elif tag == SENSOR_REPORTID_ROTATION_VECTOR:
(_,_,_,i,j,k,w,_) = struct.unpack("<BBBhhhhh", r[:14])
Q14 = 2 ** -14
# print((i * Q14, j * Q14, k * Q14, w * Q14))
return (w * Q14, i * Q14, j * Q14, k * Q14)
r = r[14:]
else:
assert 0, "Bad tag %#x" % tag
def setFeature(self, reportID, timeBetweenReports, specificConfig = 0):
p = struct.pack("<BBBHIII",
SHTP_REPORT_SET_FEATURE_COMMAND,
reportID,
0, 0,
timeBetweenReports,
0, specificConfig)
# print(hexdump(p))
self.sendPacket(CHANNEL_CONTROL, p)
def sendPacket(self, channel, data):
# print('send on', channel, 'seq', self.seqno[channel])
self.i2.start(self.a, 0)
self.i2.write(struct.pack("<HBB", 4 + len(data), channel, self.seqno[channel]))
self.i2.write(data)
self.i2.stop()
self.seqno[channel] += 1
def receivePacket(self):
if not self.i2.start(self.a, 1):
self.i2.stop()
return None
hdr = self.i2.read(4)
self.i2.stop()
length, channel, sequence = (struct.unpack("<HBB", hdr))
length &= 0x7fff
# print()
# print('length', length)
# print('channel', channel)
# print('sequence', sequence)
if length == 0 or channel not in range(8):
return None
# self.seqno[channel] = sequence + 1
self.i2.start(self.a, 1)
data = self.i2.read(length)
self.i2.stop()
# print(len(data), repr(data))
# print(hexdump(data[4:]))
return data[4:]
def showpacket(self, data):
tag = data[0]
print('tag', tag)
r = data[1:]
if tag == 0:
self.showpacket_00(r)
else:
assert False, "Cannot show packet %02x" % tag
def showpacket_00(self, ad):
while ad:
(T, L) = struct.unpack("BB", ad[:2])
V = ad[2:2+L]
decoder = {
1: lambda: ("GUID", struct.unpack("I", V)),
2: lambda: ("MaxCargoPlusHeaderWrite", struct.unpack("H", V)),
3: lambda: ("MaxCargoPlusHeaderRead", struct.unpack("H", V)),
4: lambda: ("MaxTransferWrite", struct.unpack("H", V)),
5: lambda: ("MaxTransferRead", struct.unpack("H", V)),
6: lambda: ("NormalChannel", struct.unpack("B", V)),
7: lambda: ("WakeChannel", struct.unpack("B", V)),
8: lambda: ("AppName", (V[:-1].decode(), )),
9: lambda: ("ChannelName", (V[:-1].decode(), )),
0x80: lambda: ("SHTP Version", (V[:-1].decode(), )),
}.get(T, lambda: (str(T), (V,)))
(nm,f) = decoder()
print("%5d %-26s: %r" % ((len(ad), nm, ) + f))
ad = ad[2+L:]
if __name__ == '__main__':
i2 = I2CDriver(sys.argv[1])
d = BNO080(i2)
while True:
print('quaternion:', d.read_quaternion())

BIN
python/samples/cp437-8x8 Normal file

Binary file not shown.

3
python/samples/go Normal file
View File

@ -0,0 +1,3 @@
python led8x8.py /dev/ttyUSB0
# python EDS-take-a-ticket.py /dev/ttyUSB0
# python i2cgui.py

View File

@ -240,7 +240,6 @@ class Frame(wx.Frame):
def read(self, e): def read(self, e):
n = int(self.rxCount.GetValue()) n = int(self.rxCount.GetValue())
if self.addr is not None: if self.addr is not None:
print("read", n)
self.start(1) self.start(1)
r = self.sd.read(n) r = self.sd.read(n)
bb = struct.unpack("B"*n, r) bb = struct.unpack("B"*n, r)

82
python/samples/lcd1602.py Normal file
View File

@ -0,0 +1,82 @@
"""
Example for LCD1602, in which a PCF8574 I/O expander drives a HD44780.
Available from various vendors.
Note that the modules require a 5V VCC; they don't function using the
3.3V VCC of I2CDriver.
"""
import sys
import time
import struct
from i2cdriver import I2CDriver
class HD44780:
def __init__(self, i2, a = 0x27):
self.i2 = i2
self.a = a
self.nybble(3) # Enter 4-bit mode
self.nybble(3)
self.nybble(3)
self.nybble(2)
self.cmd(0x28) # 2 lines, 5x8 dot matrix
self.cmd(0x0c) # display on
self.cmd(0x06) # inc cursor to right when writing and don't scroll
self.cmd(0x80) # set cursor to row 1, column 1
self.clear()
def clear(self):
""" Clear the screen """
self.cmd(0x01)
time.sleep(.003)
def show(self, line, text):
""" Send string to LCD. Newline wraps to second line"""
self.cmd({0:0x80, 1:0xc0}[line])
for c in text:
self.data(ord(c))
def cmd(self, b):
self.nybble(b >> 4)
self.nybble(b & 0xf)
time.sleep(.000053)
def data(self, b):
self.nybble(b >> 4, 1)
self.nybble(b & 0xf, 1)
# The PCF8574 outputs are connected to the HD44780
# pins as follows:
# P0 RS (0: command, 1: data)
# P1 R/W (0: write, 1: read)
# P2 Enable/CLK
# P3 Backlight control
# P4-7 D4-D7
def nybble(self, n, rs = 0):
bl = 8 | rs
self.port(
bl | (n << 4),
bl | (n << 4) | 4,
bl | (n << 4)
)
def port(self, *bb):
# Write bytes to port, setting the PCF8574 outputs
self.i2.start(self.a, 0)
self.i2.write(bb)
self.i2.stop()
if __name__ == '__main__':
i2 = I2CDriver(sys.argv[1])
d = HD44780(i2)
d.show(0, "HELLO WORLD")
time.sleep(.5)
d.show(1, "0123456789012345")

37
python/samples/led8x8.py Normal file
View File

@ -0,0 +1,37 @@
"""
Example for 8x8 LED Matrix modules, based on HT16K33.
Available from multiple vendors.
"""
import sys
import time
from i2cdriver import I2CDriver
font = open("cp437-8x8", "rb").read()
from ht16k33 import HT16K33
class led8x8(HT16K33):
def image(self, bb):
""" Set the pixels to the bytes bb """
def swiz(b):
bs = [str((b >> n) & 1) for n in range(8)]
return int(bs[7] + bs[0] + bs[1] + bs[2] + bs[3] + bs[4] + bs[5] + bs[6], 2)
bb = [swiz(b) for b in bb]
self.load([b for s in zip(bb,bb) for b in s])
def char(self, c):
""" Set the pixels to character c """
n = ord(c)
ch = font[n * 8:n * 8 + 8]
self.image(ch)
if __name__ == '__main__':
i2 = I2CDriver(sys.argv[1])
d = led8x8(i2)
for c in "I2C":
d.char(c)
time.sleep(1)

BIN
python/samples/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

35
python/samples/mag3110.py Normal file
View File

@ -0,0 +1,35 @@
"""
Example Xtrinsic MAG3110 Three-Axis Digital Magnetometer
Breakout available from Sparkfun.
"""
import sys
import struct
import time
from i2cdriver import I2CDriver, EDS
class MAG3110:
def __init__(self, i2, a = 0x0e):
self.i2 = i2
self.a = a
self.i2.regwr(self.a, 0x10, 0b00000001) # CTRL_REG1. ACTIVE mode, 80 Hz conversions
def rd(self):
""" Read the measurement STATUS_REG and OUT_X,Y,Z """
return self.i2.regrd(self.a, 0x00, ">B3h")
def measurement(self):
""" Wait for a new field reading, return the (x,y,z) """
while True:
(status, x, y, z) = self.rd()
if status & 8:
return (x, y, z)
if __name__ == '__main__':
i2 = I2CDriver(sys.argv[1])
i2.scan()
d = MAG3110(i2)
while 1:
print(d.measurement())

107
python/samples/oled.py Normal file
View File

@ -0,0 +1,107 @@
"""
Example for 128X64OLED Module
Available from multiple vendors, e.g. DIYMall
This example loads the I2CDriver logo onto the display,
and flashes it four times.
"""
import sys
import time
from PIL import Image, ImageChops
from i2cdriver import I2CDriver
SETCONTRAST = 0x81
DISPLAYALLON_RESUME = 0xA4
DISPLAYALLON = 0xA5
NORMALDISPLAY = 0xA6
INVERTDISPLAY = 0xA7
DISPLAYOFF = 0xAE
DISPLAYON = 0xAF
SETDISPLAYOFFSET = 0xD3
SETCOMPINS = 0xDA
SETVCOMDETECT = 0xDB
SETDISPLAYCLOCKDIV = 0xD5
SETPRECHARGE = 0xD9
SETMULTIPLEX = 0xA8
SETLOWCOLUMN = 0x00
SETHIGHCOLUMN = 0x10
SETSTARTLINE = 0x40
MEMORYMODE = 0x20
COLUMNADDR = 0x21
PAGEADDR = 0x22
COMSCANINC = 0xC0
COMSCANDEC = 0xC8
SEGREMAP = 0xA0
CHARGEPUMP = 0x8D
class OLED:
def __init__(self, i2, a = 0x3c):
self.i2 = i2
self.a = a
self.command(DISPLAYOFF)
self.command(SETDISPLAYCLOCKDIV, 0x80) # the suggested ratio 0x80
self.command(SETMULTIPLEX, 0x3f)
self.command(SETDISPLAYOFFSET, 0)
self.command(SETSTARTLINE | 0x0)
self.command(CHARGEPUMP, 0x14)
self.command(MEMORYMODE, 0)
self.command(SEGREMAP | 0x1)
self.command(COMSCANDEC)
self.command(SETCOMPINS, 0x12)
self.command(SETCONTRAST, 0xcf)
self.command(SETVCOMDETECT, 0x40)
self.command(DISPLAYALLON_RESUME)
self.command(NORMALDISPLAY)
self.im = Image.new("1", (128,64), 1)
self.cls()
def command(self, *c):
assert(self.i2.start(self.a, 0))
assert(self.i2.write((0,) + c))
self.i2.stop()
def image(self, im):
for p in range(8):
pr = self.im.crop((0,8*p,128,8*p+8)).transpose(Image.ROTATE_270)
bb = im.crop((0,8*p,128,8*p+8)).transpose(Image.ROTATE_270)
diff = ImageChops.difference(pr, bb)
di = diff.getbbox()
if di is not None:
(x0, y0, x1, y1) = di
self.command(COLUMNADDR)
self.command(y0)
self.command(y1 - 1)
self.command(PAGEADDR)
self.command(p)
self.command(p + 1)
self.i2.start(self.a, 0)
self.i2.write([0x40])
self.i2.write(bb.tobytes()[y0:y1])
self.i2.stop()
self.im = im
self.command(DISPLAYON)
def cls(self):
self.image(Image.new("1", (128,64), 0))
if __name__ == '__main__':
i2 = I2CDriver(sys.argv[1])
d = OLED(i2)
d.image(Image.open("logo.png").convert("1"))
for i in range(4):
d.command(INVERTDISPLAY)
time.sleep(.5)
d.command(NORMALDISPLAY)
time.sleep(.5)

View File

@ -0,0 +1,43 @@
"""
Example for Qwiic Joystick
Available from Sparkfun.
"""
import sys
import time
import struct
from i2cdriver import I2CDriver
class Joystick:
def __init__(self, i2, a = 0x20):
self.i2 = i2
self.a = a
def axis(self, i):
self.i2.start(self.a, 0)
self.i2.write([i])
self.i2.stop() # Note: their firmware cannot handle an I2C restart
self.i2.start(self.a, 1)
(r,) = struct.unpack(">H", self.i2.read(2))
self.i2.stop()
return r
def read(self):
"""
return the joystick (x,y) position. The range is 0-1023.
The center position of the joystick is approximately 512.
"""
# Note: their firmware requires two separate reads
return (self.axis(0), self.axis(2))
if __name__ == '__main__':
i2 = I2CDriver(sys.argv[1])
d = Joystick(i2)
while 1:
print(d.read())
time.sleep(.1)

View File

@ -0,0 +1,44 @@
"""
Example for Qwiic Keypad
Available from Sparkfun.
"""
import sys
import time
import struct
from i2cdriver import I2CDriver
class Keypad:
def __init__(self, i2, a = 0x4b):
self.i2 = i2
self.a = a
def read_ts(self):
"""
Return (key, timestamp) if pressed, or None.
"""
self.i2.start(self.a, 1)
(k, age_in_ms) = struct.unpack(">BH", self.i2.read(3))
self.i2.stop()
if k != 0:
return (chr(k), time.time() - age_in_ms * .001)
else:
return None
def read(self):
r = self.read_ts()
if r:
return r[0]
else:
return None
if __name__ == '__main__':
i2 = I2CDriver(sys.argv[1])
d = Keypad(i2)
while 1:
print(d.read())
time.sleep(.1)

33
python/samples/touch.py Normal file
View File

@ -0,0 +1,33 @@
"""
Example for MPR121 Capacitive Touch Sensor.
Available from multiple vendors.
"""
import sys
import serial
import time
import struct
import random
from i2cdriver import I2CDriver
class MPR121:
def __init__(self, i2, a = 0x5a):
self.i2 = i2
self.a = a
self.i2.regwr(self.a, 0x5e, 0x0c)
def read(self):
""" Return 12 touch detection flags """
(tb,) = struct.unpack("<H", self.i2.regrd(0x5a, 0, 2))
return [((tb >> i) & 1) for i in range(12)]
if __name__ == '__main__':
i2 = I2CDriver(sys.argv[1])
i2.reset()
d = MPR121(i2)
while 1:
print(d.read())
time.sleep(.1)

View File

@ -19,9 +19,6 @@ setup(name='i2cdriver',
install_requires=['pyserial'], install_requires=['pyserial'],
py_modules = [ py_modules = [
'i2cdriver', 'i2cdriver',
'lm75b',
'EDS', 'EDS',
'ht16k33',
'bargraph',
], ],
) )